Kubernetes中Nginx配置热加载

2022-09-21 11:15:27 浏览数 (1)

Nginx本身是支持热更新的,通过nginx -s reload指令,实际通过向进程发送HUB信号实现不停服重新加载配置,然而在Docker或者Kubernetes中,每次都需要进容器执行nginx -s reload指令,单docker容器还好说,可以在外面通过exec指定容器执行该指令进行热加载,Kubernetes的话,就比较难受了

今天介绍一下Kubernetes中Nginx热加载配置的处理方法——reloader

reloader地址:https://github.com/stakater/Reloader

reloader主要就是用来监测ConfigMap或Secret的变化,然后对相关DeploymentConfig的Deployment、DaemonSet执行滚动升级

reloader需要kubernetes1.9以上的版本才支持

使用方法

首先是安装部署reloader

代码语言:javascript复制
# 直接通过官方yaml文件部署
kubectl apply -f https://raw.githubusercontent.com/stakater/Reloader/master/deployments/kubernetes/reloader.yaml

默认情况下reloader是部署在default命名空间,但是它是监控所有命名空间的configmaps和secrets

当然,如果不想监控某个configmap或secret,可以通过--resources-to-ignore=configMaps/secrets来忽略某个资源

部署成功后,就可以直接使用了,我提前部署了nginx和configmap

这是目前的配置,看一下Nginx目前的配置

接着,我修改Nginx的Deployment,添加reloader,监听nginx-config这个ConfigMap,执行reload

代码语言:javascript复制
{
  "kind": "Deployment",
  "apiVersion": "extensions/v1beta1",
  "metadata": {
    "name": "nginx",
    "namespace": "default",
    "selfLink": "/apis/extensions/v1beta1/namespaces/default/deployments/nginx",
    "uid": "7eee5fa8-7514-11ec-a916-0210d5e9ca3b",
    "resourceVersion": "286141",
    "generation": 10,
    "creationTimestamp": "2022-01-14T08:32:23Z",
    "labels": {
      "k8s-app": "nginx"
    },
    "annotations": {
      "deployment.kubernetes.io/revision": "9",
      "description": "nginx应用"
      # 主要是这行
      "reloader.stakater.com/reload": "nginx-config"
    }
  },
  "spec": {
    "replicas": 1,
    "selector": {
      "matchLabels": {
        "k8s-app": "nginx"
      }
    }
    ……

然后apply该Deployment,之后我们去更新ConfigMap,更新nginx配置文件

更新完成,去掉proxy_redirect,然后去看nginx容器是否执行滚动更新

可以看到,nginx执行了滚动更新,接着看下nginx配置文件是否更新

这样很简单的通过reloader就可以实现Nginx的配置热加载

除了这种方法,常见的方法还有使用sidecar,通过sidecar去做的话,需要自己写监听脚本,比较麻烦,但是有时候也相对灵活,这里也附一个sidecar的python脚本

代码语言:javascript复制
#!/usr/bin/env python
# -*- encoding: utf8 -*-
"""
需求:nginx配置文件变化,自动更新配置文件,类似nginx -s reload
实现:
    1、用pyinotify实时监控nginx配置文件变化
    2、如果配置文件变化,给系统发送HUP来reload nginx
"""
import os
import re
import pyinotify
import logging
from threading import Timer

# Param
LOG_PATH = "/root/python/log"
CONF_PATHS = [
  "/etc/nginx",
]
DELAY = 5
SUDO = False
RELOAD_COMMAND = "nginx -s reload"
if SUDO:
  RELOAD_COMMAND = "sudo "   RELOAD_COMMAND

# Log
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
log_handler = logging.FileHandler(LOG_PATH)
log_handler.setLevel(logging.INFO)
log_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
log_handler.setFormatter(log_formatter)
logger.addHandler(log_handler)

# Reloader
def reload_nginx():
  os.system(RELOAD_COMMAND)
  logger.info("nginx is reloaded")

t = Timer(DELAY, reload_nginx)

def trigger_reload_nginx(pathname, action):
  logger.info("nginx monitor is triggered because %s is %s" % (pathname, action))
  global t
  if t.is_alive():
    t.cancel()
    t = Timer(DELAY, reload_nginx)
    t.start()
  else:
    t = Timer(DELAY, reload_nginx)
    t.start()

events = pyinotify.IN_MODIFY | pyinotify.IN_CREATE | pyinotify.IN_DELETE

watcher = pyinotify.WatchManager()
watcher.add_watch(CONF_PATHS, events, rec=True, auto_add=True)

class EventHandler(pyinotify.ProcessEvent):
  def process_default(self, event):
    if event.name.endswith(".conf"):
      if event.mask == pyinotify.IN_CREATE:
        action = "created"
      if event.mask == pyinotify.IN_MODIFY:
        action = "modified"
      if event.mask == pyinotify.IN_DELETE:
        action = "deleted"
      trigger_reload_nginx(event.pathname, action)

handler = EventHandler()
notifier = pyinotify.Notifier(watcher, handler)

# Start
logger.info("Start Monitoring")
notifier.loop()

如果喜欢用go的,这里也提供go脚本

代码语言:javascript复制
package main

import (
    "log"
    "os"
    "path/filepath"
    "syscall"

    "github.com/fsnotify/fsnotify"
    proc "github.com/shirou/gopsutil/process"
)

const (
    nginxProcessName = "nginx"
    defaultNginxConfPath = "/etc/nginx"
    watchPathEnvVarName = "WATCH_NGINX_CONF_PATH"
)

var stderrLogger = log.New(os.Stderr, "error: ", log.Lshortfile)
var stdoutLogger = log.New(os.Stdout, "", log.Lshortfile)

func getMasterNginxPid() (int, error) {
    processes, processesErr := proc.Processes()
    if processesErr != nil {
        return 0, processesErr
    }

    nginxProcesses := map[int32]int32{}

    for _, process := range processes {
        processName, processNameErr := process.Name()
        if processNameErr != nil {
            return 0, processNameErr
        }

        if processName == nginxProcessName {
            ppid, ppidErr := process.Ppid()

            if ppidErr != nil {
                return 0, ppidErr
            }

            nginxProcesses[process.Pid] = ppid
        }
    }

    var masterNginxPid int32

    for pid, ppid := range nginxProcesses {
        if ppid == 0 {
            masterNginxPid = pid

            break
        }
    }

    stdoutLogger.Println("found master nginx pid:", masterNginxPid)

    return int(masterNginxPid), nil
}

func signalNginxReload(pid int) error {
    stdoutLogger.Printf("signaling master nginx process (pid: %d) -> SIGHUPn", pid)
    nginxProcess, nginxProcessErr := os.FindProcess(pid)

    if nginxProcessErr != nil {
        return nginxProcessErr
    }

    return nginxProcess.Signal(syscall.SIGHUP)
}

func main() {
    watcher, watcherErr := fsnotify.NewWatcher()
    if watcherErr != nil {
        stderrLogger.Fatal(watcherErr)
    }
    defer watcher.Close()

    done := make(chan bool)
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }

                if event.Op&fsnotify.Create == fsnotify.Create {
                    if filepath.Base(event.Name) == "..data" {
                        stdoutLogger.Println("config map updated")

                        nginxPid, nginxPidErr := getMasterNginxPid()
                        if nginxPidErr != nil {
                            stderrLogger.Printf("getting master nginx pid failed: %s", nginxPidErr.Error())

                            continue
                        }

                        if err := signalNginxReload(nginxPid); err != nil {
                            stderrLogger.Printf("signaling master nginx process failed: %s", err)
                        }
                    }
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                stderrLogger.Printf("received watcher.Error: %s", err)
            }
        }
    }()

    pathToWatch, ok := os.LookupEnv(watchPathEnvVarName)
    if !ok {
        pathToWatch = defaultNginxConfPath
    }

    stdoutLogger.Printf("adding path: `%s` to watchn", pathToWatch)

    if err := watcher.Add(pathToWatch); err != nil {
        stderrLogger.Fatal(err)
    }
    <-done
}

ok,今天的内容就到这里,如果需要脚本,或sidecar的镜像制作,可以加群联系我,欢迎各位大佬关注转发

0 人点赞