# 用 Sidecar 应用 Configmap 更新

在 Kubernetes 的使用过程中，很多人会使用 Configmap 资源来进行配置文件的加载。Configmap 对象是支持热更新的，也就是说，对 Configmap 的变更，会同时反应到加载该 Configmap 的 Pod 之中。但美中不足的是，很多应用都不会检测配置文件的更新，因此就算是通过对 Configmap 的变更，完成了配置文件的修改，应用还是无法做出即时的响应的。可以在外部进行滚动更新；或者改写业务容器，监控文件变化之后重新启动业务进程。

在 Kubernetes 1.10 中新增的 Pod 内共享进程命名空间的功能，给这个问题带来了一点新思路：做一个 Sidecar 用于对配置文件进行监控，发现文件变化之后，发送重新载入的信号给业务进程，要求业务进程自行刷新。这样就无需对业务容器所在镜像进行修改了。

> 这种方法当然也有个局限性，需要业务进程支持这种信号。

下面以 Apache 为例，看看这种方式的用法。

## 创建 Configmap

用一个简化的 `httpd.conf` 文件，生成 Configmap，例如：

kubectl create cm apache --from-file httpd.conf

如此就生成了一个名为 apache 的 Configmap。

## 创建 Sidecar 容器镜像

这个镜像要完成的任务有两个：监控文件变化，如果内容变化，则发送信号给业务进程。文件内容变化的监控，可以用哈希码或者 inotify 调用来完成，这里使用 `inotifywait` 命令做一个死循环，发现特定事件后，则发出信号：

```bash
#!/bin/sh
while :
do
  # 获取文件名称
  REAL=`readlink -f ${FILE}`
  # 监控指定事件
  inotifywait -e delete_self "${REAL}"
  # 获取特定进程名称的 PID
  PID=`pgrep ${PROCESS} | head -1`
  # 发送信号
  kill "-${SIGNAL}" "${PID}"
done
```

> 这里没有用监控本地文件的 `-m` 或者 `-e modify` 事件，而是用了 `delete_self`，这是 Configmap 加载生成文件的差异，也可以考虑用环境变量来替换这一事件。

然后构建镜像：

```Dockerfile
FROM alpine
RUN apk add --update inotify-tools
ENV FILE="/tmp" PROCESS="httpd" SIGNAL="USR1"
COPY entry.sh /usr/local/bin
CMD ["/usr/local/bin/entry.sh"]
```

这里假设镜像名称为 `dustise/inotify:latest`。

## 创建实验负载

我们选择 Apache 作为业务应用的示范，它能够接受 `USR1` 信号进行重新载入。

```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: apache
spec:
  selector:
    matchLabels:
      app: apache
  template:
    metadata:
      labels:
        app: apache
    spec:
      shareProcessNamespace: true
      containers:
      - name: apache
        image: httpd:alpine
        ports:
        - containerPort: 80
        volumeMounts:
          - name: apache
            mountPath: /usr/local/apache2/conf/
      - name: refresh
        image: dustise/inotify
        securityContext:
          capabilities:
            add:
            - SYS_PTRACE
        volumeMounts:
          - name: apache
            mountPath: /etc/httpd
        env:
          - name: FILE
            value: "/etc/httpd/httpd.conf"
          - name: PROCESS
            value: "httpd"
          - name: SIGNAL
            value: "USR1"
      volumes:
        - name: apache
          configMap:
            name: apache
---
apiVersion: v1
kind: Service
...
  type: ClusterIP
```

这段代码：

* 在 `template.spec` 中加入了 `shareProcessNamespace: true`，表示启用进行命名空间共享功能；
    
* 新建了一个伴行的 Sidecar 容器；
    
* Apache 和 Sidecar 共享来自同一个 Configmap 的配置文件，根据加载情况为 Sidecar 定义了环境变量。
    

## 测试一下

接下来可以使用 `kubectl logs` 命令来监控两个容器的日志输出：

```shell
$ kubectl logs -f apache-6b8b68c857-dp6xx -c refresh
Setting up watches.
Watches established.

$ kubectl logs -f apache-6b8b68c857-dp6xx -c apache
...
[Wed May 15 18:46:47.795261 2019] [mpm_event:notice] [pid 7:tid 139810635549544] AH00489: Apache/2.4.39 (Unix) configured -- resuming normal operations
[Wed May 15 18:46:47.795330 2019] [core:notice] [pid 7:tid 139810635549544] AH00094: Command line: 'httpd -D FOREGROUND'
```

然后使用 `kubectl edit cm apache`，修改配置文件（例如删除点注释）。稍候片刻，发现两个容器的输出都发生了变化：

### Sidecar

```bash
/etc/httpd/..2019_05_15_18_43_33.773288813/httpd.conf DELETE_SELF
Setting up watches.
Watches established.
```

脚本检测到了配置文件发生了删除事件，发送信号，并重新启动监控。

### Apache

```bash
[Wed May 15 18:46:47.775392 2019] [mpm_event:notice] [pid 7:tid 139810635549544] AH00493: SIGUSR1 received.  Doing graceful restart
```

Apache 收到了 USR1 信号，进行了优雅重启。

## 结论

对于支持信号控制的第软件，例如 Nginx、Gunicorn、HA-Proxy 等都可以使用这种方式来完成配置刷新工作。能够有效的避免重启或修改业务应用的老大难问题。
