如今咱们使用容器很是频繁,偶尔有一些需求须要更改容器镜像中的一些行为,也许是一个很小的变化,通常咱们能想到的就是从新构建镜像,可是这个咱们就须要从新构建发布镜像了,除了构建镜像这种方式以外其实还有其余方式能够来实现这个需求。html
初始化容器
Init Containers 是为了给 Pod 中定义的主容器提供附加功能的。它们在主容器以前执行,可使用不一样的容器镜像,若是出现任何故障,它们将阻止主容器的启动,全部的日志均可以很容易查看到,故障排除也至关简单,它们就像在 Pod 中定义的任何其余容器同样。这种方法在数据库等服务中比较经常使用,能够根据配置参数对它们进行初始化和配置。nginx
下面的例子使用一个 emptyDir
来存储由初始化容器初始化的数据。在这个示例,它只是一个简单的 echo
命令,在实际的生产环境中,多是一个脚本,作一些更复杂的事情。git
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx-init spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: initContainers: - name: prepare-webpage image: busybox:1.28 command: ["sh", "-c"] args: [ "set -x; echo '<h2>Page prepared by an init container</h2>' > /web/index.html; echo 'Init finished successfully' ", ] volumeMounts: - mountPath: /web name: web containers: - image: nginx:1.19 name: nginx volumeMounts: - mountPath: /usr/share/nginx/html/ name: web ports: - containerPort: 80 name: http volumes: - name: web emptyDir: {}
PostStart Hook
post-start hook 可用于在主容器启动后执行一些操做,它能够是在与容器相同的上下文中执行的脚本,也能够是针对定义的端点执行的 HTTP 请求,可是,不能保证回调会在容器入口点(ENTRYPOINT)以前执行。在大多数状况下,它多是一个 shell 脚本,Pod一直保持在ContainerCreating
状态,直到这个脚本结束。因为没有可用的日志,因此调试起来可能很棘手。这个方法最大的特色是,当主容器中的服务启动时,脚本就会被执行,而且能够用来与服务进行交互,经过适当的 readinessProbe
配置,这能够提供一种很好的方式,在容许任何请求以前初始化应用程序。在下面的例子中,一个启动后的钩子会执行 echo 命令,但一样这能够是任何使用容器文件系统上可用的同一组文件来执行某种初始化的东西。github
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx-hook spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.19 name: nginx ports: - containerPort: 80 name: http lifecycle: postStart: exec: command: [ "sh", "-c", "sleep 5;set -x; echo '<h2>Page prepared by a PostStart hook</h2>' > /usr/share/nginx/html/index.html", ]
Sidecar 容器
这种方法利用了 Pod 的概念 - 多个容器同时运行、共享 IPC 和网络命名空间。在 Kubernetes 生态系统中,它已经被 Istio、Consul Connect 等项目普遍使用。这里的假设是全部容器同时运行,这使得使用 sidecar 容器来修改主容器的行为变得有点棘手。但这是可行的,它能够用来与正在运行的应用程序或服务进行交互。我在 Jenkins Helm Chart 中使用了这个功能,其中有一个 sidecar 容器负责读取 ConfigMap 对象和 Configuration-as-Code
配置项。web
在下面示例中一样只是使用 echo 这个命令,不过须要注意的是,由于 sidecar 容器必须遵循 restartPolicy 设置,因此这个容器在完成动做后还必须处于运行状态,示例中咱们使用的是一个简单的 while 无限循环,在实际环境中,每每会是一个小的守护进程,像服务同样一直运行。docker
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx-sidecar spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.19 name: nginx volumeMounts: - mountPath: /usr/share/nginx/html/ name: web ports: - containerPort: 80 name: http - name: prepare-webpage image: busybox:1.28 command: ["sh", "-c"] args: [ "set -x; echo '<h2>Page prepared by a sidecar container</h2>' > /web/index.html; while :;do sleep 9999;done ", ] volumeMounts: - mountPath: /web name: web volumes: - name: web emptyDir: {}
EntryPoint
最后一种方法使用相同的容器镜像,与 PostStart Hook 相似,只是它在主应用程序或服务以前运行。咱们在容器镜像中都定义一个ENTRYPOINT
命令,咱们能够利用它来执行一些脚本,这种方式常常被不少官方镜像所使用,在这种方法中,咱们只须要预置本身的脚原本修改主容器的行为。在实际生产环境中,其实咱们能够提供一个修改后的原始入口点文件。shell
这个方法相对复杂一点,须要建立一个 ConfigMap,其中包含一个脚本内容,在主入口点以前执行。以下所示咱们修改 nginx 入口点的脚本,而后嵌入到下面的 ConfigMap 中。数据库
apiVersion: v1 kind: ConfigMap metadata: name: scripts data: prestart-script.sh: |- #!/usr/bin/env bash echo '<h2>Page prepared by a script executed before entrypoint container</h2>' > /usr/share/nginx/html/index.html # 这是 "ENTRYPOINT CMD "从主容器镜像定义中提取出来的 exec /docker-entrypoint.sh nginx -g "daemon off;"
有一点很是重要,就是最后一行与 exec,它执行的是原始的入口点脚本,必须与 Dockerfile 中定义的脚本彻底匹配,在这种状况下,它须要额外的参数,这些参数是在 CMD 中定义的。如今让咱们定义一下 Deployment 资源对象。api
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx-script spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.19 name: nginx command: ["bash", "-c", "/scripts/prestart-script.sh"] ports: - containerPort: 80 name: http volumeMounts: - mountPath: /scripts name: scripts volumes: - name: scripts configMap: name: scripts defaultMode: 0755 # <- 这个很重要
咱们用命令覆盖入口点,咱们还必须确保咱们的脚本是以适当的权限挂载的(所以须要定义 defaultMode)。bash
总结
如今咱们来总结下上面几种方式的差别。
容器讲究的是可重用性,不少时候作一些小的调整,不须要从新构建整个容器的镜像,这样发布和维护就会轻松不少。