认识 Kubernetes 之 ConfigMap 和 Secret 配置应用程序

1,124 阅读11分钟

ConfigMap 和 Secret 配置应用程序

配置容器化应用程序

  • 向容器传递命令行参数
  • 为每个容器设置自定义环境变量
  • 通过特殊类型的卷将配置文件挂载到容器中

开发一款新应用程序的初期,除了将配置嵌入应用本身,通常会以命令行参数的形式配置应用。随着配置选项数量的逐渐增多,将配置文件化。

另一种通用的传递配置选项给容器化应用程序的方法是借助环境变量。应用程序主动查找某一特定环境变量的值,而非读取配置文件或者解析命令行参数。

为何环境变量的方案会在容器环境下如此常见?通常直接在 Docker 容器中采用配置文件的方式是有些许困难的,往往需要将配置文件打入容器镜像,抑或是挂载包含该文件的卷。显然,前者类似于在应用程序源代码中硬编码配置,每次修改完配置之后需要重新构建镜像。除此之外,任何拥有镜像访问权限的人可以看到配置文件中包含的敏感信息,如证书和密钥。相比之下,挂载卷的方式更好,然而在容器启动之前需确保配置文件已写入响应的卷中。

向容器传递命令行参数

在 Docker 中定义命令与参数

迄今为止所有示例中容器运行的命令都是镜像中默认定义的。Kubernetes 可在 pod 的容器中定义并覆盖命令以满足运行不同的可执行程序,或者是以不同的命令 Kubernetes 可在 pod 的容器中定义并覆盖命令来运行不同的可执行程序,或者是以不同的命令行参数集运行。现在我们来看一下应该如何操作。

首先需要阐明的是,容器中运行的完整指令由两部分组成:命令与参数。

了解 ENTRYPOINT 与 CMD:

Dockerfile 中的两种指令分别定义命令与参数这两个部分:

  • ENTRYPOINT 定义容器启动时被调用的可执行程序。
  • CMD指定传递给 ENTRYPOINT 的参数。
// 尽管可以直接使用 CMD 指令指定镜像运行时想要执行的命令,正确的做法依旧是借助 ENTRYPOINT 指令,仅仅用 CMD 指定所需的默认参数。这样,镜像可以直接运行,无须添加任何参数:
$ docker run <image>
// 或者是添加一些参数,覆盖Dockerile中任何由CMD指定的默认参数值:
$ docker run <image> <argumen七S>

了解 shell 与 exec 形式的区别

上述两条指令均支持以下两种形式:

  • shell 形式: 如 ENTRYPOINT node app.js
  • exec 形式: 如 ENTRYPOINT ["node", "app. js"]

两者的区别在于指定的命令是否是在shell中被调用。

在 Kubernetes 中覆盖命令和参数

kind: Pod
spec:
  conatiners:
    - image: some/image
      command: ["/bin/command"]       // 等同于 ENTRYPOINT
      args: ["arg1", "arg2", "arg3"]  // 等同于 CMD
// 少量参数值的设置可以使用上述的数组表示。多参数值情况下可以采用如下标记:
args:
  - foo
  - bar
  - "15"
// 提示字符串值无须用引号标记,数值需要。

注意 command 和 args 字段在 pod 创建后无法被修改。

为容器设置环境变量

如前所述,容器化应用通常会使用环境变量作为配置源。Kubernetes 允许为 pod 中的每一个容器都指定自定义的环境变量集合。

注意与容器的命令和参数设置相同,环境变量列表无法在 pod 创建后被修改。

在容器定义中指定环境变量

kind: Pod
spec:
  containers:
    - image: some/image
      env:
        - name: envName
          value: envValue
      name: podName

注意 不要忘记在每个容器中,Kubernetes 会自动暴露相同命名空间下每个 service 对应的环境变量。这些环境变量基本上可以被看作自动注入的配置。

在环境变量值中引用其他环境变量

env:
  - name: FIRST_VAR
    value: "foo"
  - name: SECOND_VAR
    value: "$(FIRST_VAR)bar"

了解硬编码环境变量的不足之处

pod 定义硬编码意味着需要有效区分生产环境与开发过程中的 pod 定义。为了能在多个环境下复用 pod 的定义,需要将配置从 pod 定义描述中解藕出来。幸运的是,你可以通过一种叫作 ConfigMap 资源对象完成解耦,用 valueFrom 字段替代 value 字段使 ConfigMap 成为环境变量值的来源。接下来将学习到这一用法。

利用 ConfigMap 解耦配置

应用配置的关键在于能够在多个环境中区分配置选项,将配置从应用程序源码中分离,可频繁变更配置值。如果将 pod 定义描述看作是应用程序源代码,显然需要将配置移出 pod 定义。微服务架构下正是如此,该架构定义了如何将多个个体组件组合成功能系统。

ConfigMap 介绍

Kubernetes 允许将配置选项分离到单独的资源对象 ConfigMap 中,本质上就是一个键/值对映射,值可以是短字面量,也可以是完整的配置文件。

应用无须直接读取 ConfigMap 甚至根本不需要知道其是否存在。映射的内容通过环境变量或者卷文件的形式传递给容器,而并非直接传递给容器。命令行参数的定义中可以通过$(ENV_VAR)语法引用环境变量,因而可以达到将 ConfigMap 的条目当作命令行参数传递给进程的效果。

当然,应用程序同样可以通过 k8s Rest API 按需直接读取 ConfigMap 的内容。不过除非是需求如此,应尽可能使你的应用保持对 kubernetes 的无感知。

不管应用具体是如何使用 ConfigMap 的,将配置存放在独立的资源对象中有助于在不同环境(开发、测试、质量保障和生产等)下拥有多份同名配置清单。pod 是通过名称引用 ConfigMap 的,因此可以在多环境下使用相同的 pod 定义描述,同时保持不同的配置值以适应不同环境。

创建 ConfigMap

注意 ConfigMap 中的键名必须是一个合法的 DNS 子域,仅包含数字字母、破折号、下画线以及圆点,首位的圆点符号是可选的。

使用命令行创建

  • --from-literal: 使用字面量创建
  • om-file: 使用文件或文件夹创建
$ kubectl create configmap my-config --from-literal=some=thing --from-file=foo.json --from-file=bar=forbar.conf --from-file=config/ 

使用描述文件创建

apiVersion: v1
data:
  someKey: someValue
kind: ConfigMap
metadata:
  name: configMapName

给容器传递 ConfigMap 条目作为环境变量

如何将映射中的值传递给 pod 的容器?有三种方法。首先尝试最为简单的一种——设置环境变量。

apiVersion: v1
kind: Pod
metadata:
  name: podName
spec:
  containers:
    - image: some/image
      env:
        - name: INTERVAL          // 设置环境变量 INTERVAL
          valueFrom:              //  ConfigMap 初始化,不设置固定值
            configMapKeyRef:
              name: configMapName // 引用 ConfigMap 的名称
              key: someKey        // 环境变量值被设置为 ConfigMap 下的对应键的值

在 pod 中引用不存在的 ConfigMap

你可能会好奇如果创建 pod 时引用的 ConfigMap 不存在会发生什么? Kubernetes 会正常调度 pod 并尝试运行所有的容器。然而引用不存在的 ConfigMap 的容器会启动失败,其余容器能正常启动。如果之后创建了这个缺失的 ConfigMap, 失败容器会自动启动,无须重新创建 pod。

注意 可以标记对 ConfigMap 的引用是可选的(设置 configMapKeyRef.optional: true)。这样,即使 ConfigMap 不存在,容器也能正常启动

一次性传递 ConfigMap 的所有条目作为环境变量

假设 ConfigMap 包含 FOO、BAR、FOO-BAR 三个键。可以通过 envFrom 属性字段将所有条目暴露作为环境变量,而非使用前面例子中的 env 字段。示例代码如下所示

spec:
  containers:
    - images: some/image
      envFrom:
        - prefix: CONFIG_
          configMapRef:
            name: configMapName

如你所见,可以为所有的环境变量设置前缀,如本例中的 CONFIG_,容器中两个环境变量的名称为:CONFIG_FOO 与 CONFIG_BAR。

注意 前缀设置是可选的,若不设直前缀值,环境变量的名称与 ConfigMap 的键名相同

是否注意到前面说的是两个环境变量,然而 ConfigMap 拥有三个条目(FOO,BAR,FOO-BAR)?为何没有对应 FOO-BAR 条目的环境变量呢?

原因在于 CONFIG_FOO-BAR 包含破折号,这并不是一个合法的环境变量名称。k8s 不会主动转换键名(例如不会将破折号转换为下画线)。如果 ConfigMap 的某键名格式不正确,创建环境变量时会忽略对应的条目(忽略时不会发出事件通知)

传递 ConfigMap 条目作为命令行参数

现在让我们来看下如何将 ConfigMap 中的值作为参数值传递给运行在容器中的主进程。在字段 pod.spec.containers.args 中无法直接引用 ConfigMap 的条目,但是可以利用 ConfigMap 条目初始化某个环境变量,然后再在参数字段中引用该环境变量。

spec:
  containers:
    - image: some/image:args      // 使用第一个参数读取值的镜像
      env:
        - name: INTERVAL
          valueFrom:
            configMapKeyRef:
              name: configMapName
              key: someKey
      args: ["$(INTERVAL)"]

使用 configMap 卷将条目暴露为文件

环境变量或者命令行参数值作为配置值通常适用于变量值较短的场景。由于 ConfigMap 中可以包含完整的配置文件内容,当你想要将其暴露给容器时,可以借助前面章节提到过的一种称为 configMap 卷的特殊卷格式。

ConfigMap 卷会将 ConfigMap 中的每个条目均暴露成一个文件。运行在容器中的进程可通过读取文件内容获得对应的条目值。

尽管这种方法主要适用于传递较大的配置文件给容器,同样可用于传递较短的变量值。

创建 ConfigMap

使用配置文件配置运行在 fortune pod 的 Web 服务器容器中的 Nginx web 服务器。如果想要让 Nginx 服务器压缩传递给客户端的响应,Nginx 的配置文件需开启压缩配置,如下面的代码清单所示。

// my-nginx-config.conf
server {
  listen           80;
  server_name      www.kubia-example.com;
  
  gzip on;
  gzip_types       text/plain application/xml;  // 开启对文本文件 XML 文件的 gzip 压缩
  
  location / {
    root   /usr/share/nginx/html
    index  index.html index.html
  }
}

创建一个新文件夹 confimap-files 并将上面的配置文件存储于 configmap-files/my-nginx-config.conf。另外在该文件夹中添加一个名为 sleep-interval 的文本文件,写入值为 25,ConfigMap 同样包含条目 sleep-interval。

$ kubectl create configmap fortune-config --from-file=configmap-files

ConfigMap 包含两个条目,条目的键名与文件名相同 。接下来将在 pod 的容器中使用该 ConfigMap。

在卷内使用 ConfigMap 的条目

创建包含 ConfigMap 条目内容的卷只需要创建一个引用 ConfigMap 名称的卷并挂载到容器中。已经学会了如何创建及挂载卷,接下来要学习的仅是如何用 ConfigMap 的条目初始化卷。

Nginx 需读取配置文件 /etc/nginx/nginx.conf, 而 Nginx 镜像内的这个文件包含默认配置,并不想完全覆盖这个配置文件。幸运的是,默认配置文件会自动嵌入子文件夹 /etc/nginx/conf.d/ 下的所有 .conf 文件,因此只需要将你的配置文件置于该子文件夹中即可。

apiServer: v1
kind: Pod
metadata:
  name: fortune-configmap-volume
spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    ...
    - name: config
      mountPath: /etc/nginx/conf.d      // 挂载 configMap 卷至这个位置
      readOnly: true
    ...
    volumes:
    ...
    - name: config
      configMap:
        name: fortune-config
    ...

示例中:pod 定义中包含了引用 fortune-config ConfigMap 的卷,需要被挂载到文件夹 etc/nginx/conf.d 下让 Nginx 服务器使用它。

检查 Nginx 是否使用被挂载的配置文件

现在的 web 服务器应该已经被配置为会压缩响应,可以将 localhost:8080 转发到 pod 的 80 端口, 利用 curl 检查服务器响应来验证配置是否生效。如果响应头信息中包含 Content-Encoding: gzip 则说明响应被压缩

卷内暴露指定的 ConfigMap 条目

可以创建仅包含 ConfigMap 中部分条目的 configMap 卷,去分别配置同一 pod 中的不同容器。

通过卷的 items 属性能够指定哪些条目会被暴露作为 configMap 卷中的文件,如下面的代码清单所示:

volumes:
- name: config
  configMap:
    name: configMapName
    items:                       // 选择包含在卷中的条目
    - key: my-nginx-config.conf  // 该键对应的条目被包含
      path: gzip.conf            // 条目的值被存储在该文件中

指定单个条目时需同时设置条目的键名称以及对应的文件名。如果采用上面的配置文件创建 pod, /etc/nginx/conf.d文件夹是比较干净的,仅包含所需的 gzip.conf 文件。

挂载某一文件夹会隐藏该文件夹中已存在的文件

这里有一件重要的事情需要讨论。在当前与此前的示例中,将卷挂载至某个文件夹,意味着容器镜像中 /etc/nginx/conf.d 文件夹下原本存在的任何文件都会被隐藏。

Linux 系统挂载文件系统至非空文件夹时通常表现如此。文件夹中只会包含被挂载文件系统中的文件,即便文件夹中原本的文件是不可访问的也是同样如此。

本示例中,这种现象并不会带来比较糟糕的副作用。不过假设挂载文件夹是 /etc, 该文件夹通常包含不少重要文件。由于/etc 下的所有文件不存在,容器极大可能会损坏。如果你希望添加文件至某个文件夹如 /etc, 绝不能采用这种方法。

ConfigMap 独立条目作为文件被挂载且不隐藏文件夹中的其他文件

顺理成章,你会好奇如何能挂载 CofigMap 对应文件至现有文件夹的同时不会隐藏现有文件。volumeMount 额外的 subPath 字段可以被用作挂载卷中的某个独立文件或者是文件夹,无须挂载完整卷。

假设拥有一个包含文件 mycofig.conf 的 configMap 卷,希望能将其添加为 /etc 文件夹下的文件 someconfig.conf。通过属性 subPath 可以将该文件挂载的同时又不影响文件夹中的其他文件。pod 定义中的相关部分如下面的代码清单所示。

spec:
  containers:
  - image: some/image
    volumeMounts:
    - name: myvolume
      mountPath: /etc/someconfig.config  // 挂载至某一文件,而不是文件夹
      subPath: myconfig.conf             // 仅挂载指定的条目 myconfig.conf, 并非完整的卷

挂载任意一种卷时均可以使用 subPath 属性。可以选择挂载部分卷而不是挂载完整的卷。不过这种独立文件的挂载方式会带来文件更新上的缺陷,你会在接下来的小节中学习到更多的相关知识,在这里还是先要说一些文件权限问题对 configMap 卷的讨论进行收尾。

为 configMap 卷中的文件设置权限

configMap 卷中所有文件的权限默认被设置为644 (-rw-r-r--)。 可以通过卷规格定义中的 defaultMode 属性改变默认权限,如下面的代码清单所示。

volumes:
- name: config
  configMap:
    name: configMapName
    defaultMode: "6600"    // 设置所有文件的权限为 -rm-rm------

ConfigMap 通常被用作存储非敏感数据,不过依旧可能希望仅限于文件拥有者的用户和组可读写,正如上面的例子所示。

更新应用配置且不重启应用程序

在此之前提到过,使用环境变量或者命令行参数作为配置源的弊端在于无法在进程运行时更新配置。将 ConfigMap 暴露为卷可以达到配置热更新的效果,无须重新创建 pod 或者重启容器。ConfigMap 被更新之后,卷中引用它的所有文件也会相应更新,进程发现文件被改变之后进行重载。Kubemetes 同样支待文件更新之后手动通知容器。

警告请注意笔者在写这段的时候, 更新ConfigMap之后对应文件的更新耗时会出人意杜地长(往往需要数分钟)。

了解文件被自动更新的过程

你可能会疑惑在 Kubernetes 更新完 configMap 卷中的所有文件之前,应用是否会监听到文件变化并主动进行重载。幸运的是,这不会发生,所有的文件会被自动一次性更新。Kubernetes 通过符号链接做到这一点。Kubernetes 通过符号链接做到这一点。如果尝试列出 configMap 卷挂载位置的所有文件,会看到如下内容。

$ kubectl exec -it fortune-configmap-volume -c web-server --ls-lA /e七c/nginx/conf.d
total 4
drwxr-xr-x ... 12:15 ..4984_09_04_12_15_06.865837643
lrwxrwxrwx ... 12:15 ..data -> ..4984_09_04_12_15_06.865837643
lrwxrwxrwx ... 12:15 my-nginx-config.conf -> ..data/my-nginx-config.conf
lrwxrwxrwx ... 12:15 sleep-interval -> ..data/sleep-interval

可以看到,被挂载的 configMap 卷中的文件是 ..data文件夹中文件的符号链接, 而 ..data文件夹同样是 ..4984_09_04_something 的符号链接。每当 ConfigMap 被更新后,Kubernetes 会创建一个这样的文件夹,写入所有文件并重新将符号 ..data 链接至新文件夹,通过这种方式可以一次性修改所有文件。

挂载至已存在文件夹的文件不会被更新

涉及到更新 configMap 卷需要提出一个警告:如果挂载的是容器中的单个文件而不是完整的卷,ConfigMap 更新之后对应的文件不会被更新!至少在写本章节的时候表现如此。

如果现在你需要挂载单个文件并且在修改源 ConfigMap 的同时会自动修改这个文件,一种方案是挂载完整卷至不同的文件夹并创建指向所需文件的符号链接。符号链接可以原生创建在容器镜像中,也可以在容器启动时创建。

了解更新 ConfigMap 的影响

容器的一个比较重要的特性是其不变性,从同一镜像启动的多个容器之间不存在任何差异。那么通过修改被运行容器所使用的 ConfigMap 来打破这种不变性的行为是否是错误的?

关键点在于应用是否支持重载配置。ConfigMap 更新之后创建的 pod 会使用新配置,而之前的 pod 依旧使用旧配置,这会导致运行中的不同实例的配置不同。这也不仅限于新 pod, 如果 pod 中的容器因为某种原因重启了,新进程同样会使用新配置。因此,如果应用不支持主动重载配置,那么修改某些运行 pod 所使用的 ConfigMap 并不是一个好主意。

如果应用支持主动重载配置,那么修改 ConfigMap 的行为就算不了什么。不过有一点仍需注意,由于 configMap 卷中文件的更新行为对于所有运行中示例而言不是同步的,因此不同 pod 中的文件可能会在长达一分钟的时间内出现不一致的清况。

使用 Secret 给容器传递敏感数据

到目前为止传递给容器的所有信息都是比较常规的非敏感数据。然而配置通常会包含一些敏感数据,如证书和私钥,需要确保其安全性。

介绍 Secret

为了存储与分发此类信息,Kubernetes 提供了一种称为 Secret 的单独资源对象。Secret 结构与 ConfigMap 类似,均是键/值对的映射。Secret 的使用方法也与 ConfigMap 相同,可以

  • 将 Secret 条目作为环境变量传递给容器
  • 将 Secret 条目暴露为卷中的文件

Kubernetes 通过仅仅将 Secret 分发到需要访问 Secret 的 pod 所在的机器节点来保障其安全性。另外,Secret 只会存储在节点的内存中,永不写入物理存储,这样从节点上删除 Secret 时就不需要擦除磁盘了。

对于主节点本身(尤其是 etcd), Secret 通常以非加密形式存储,这就需要保障主节点的安全从而确保存储在 Secret 中的敏感数据的安全性。这种保障不仅仅是对 etcd 存储的安全性保障,同样包括防止未授权用户对 API 服务器的访问,这是因为任何人都能通过创建 pod 并将 Secret 挂载来获得此类敏感数据。从 Kubernetes 1.7 开始,etcd 会以加密形式存储 Secret, 某种程度提高了系统的安全性。正因为如此,从 Secret 与 ConfigMap 中做出正确选择是势在必行的,选择依据相对简单:

  • 采用 ConfigMap 存储非敏感的文本配置数据。
  • 采用 Secret 存储天生敏感的数据,通过键来引用。如果一个配置文件同时包含敏感与非敏感数据,该文件应该被存储在 Secret 中。

默认令牌 Secret 介绍

首先来分析一种默认被挂载至所有容器的 Secret, 对任意一个 pod 使用命令 kubectl describe pod, 输出往往包含如下信息:

Containers:
  container:
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-cfee9
Volumes:
  default-token-cfee9:
  Type: Secret(a volume populated by a Secret)
  SecretName: default-token-cfee9
$ kubectl describe secrets
Name: default-token-cfee9
Namespace: default
Labels: <none>
Annotatons: kubernetes.io/service-account.name=default
            kubernetes.io/service-account.uid=cc04bb39-b53f-42010af00237
Type:       kubernetes.io/service-account-token
Data
===
ca.crt: 1139 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiisinR5cCI6IkpXVCJ9... 

可以看出这个 Secret 包含三个条目: ca.crt、namespace与token, 包含了从 pod 内部安全访问 Kubernetes API 服务器所需的全部信息。尽管你希望做到应用程序对 Kubernetes 的完全无感知,然而在除了直连 Kubernetes 别无他法的情况下,你将会使用到 secret 卷提供的文件。

注意 default-token Secret 默认会被挂载至每个容器 可以通过设置 pod 定义中的 automountServiceAccountToken 字段为 false, 或者设置 pod 使用的服务账户中的相同字段为 false 来关闭这种默认行为

我们已经说过 Secret 类似于 ConfigMap, 由于该 Secret 包含三个条目,可通过 kubectl exec 观察到被 secret 卷挂载的文件夹下包含三个文件:

$ kubectl exec mypod ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.ert
namespace
token

创建 Secret

$ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo

与创建 ConfigMap 的过程类似,这里创建了 一 个名为 fortune-https 的 generic Secret, 它包含有两个条目: https.key 和 https.cert, 分别对应于两个同名文件的内容。如前所述,同样可以用 --from-file=fortune-https 囊括整个文件夹中的所有文件,替代单独指定每个文件的创建方式。

对比 ConfigMap 与 Secret

Secret 条目的内容会被以 Base64 格式编码,而 ConfigMap 直接以纯文本展示。这种区别导致在处理 YAML 和 JSON 格式的 Secret 时会稍许有些麻烦,需要在设置和读取相关条目时对内容进行编解码。

为二进制数据创建Secret

采用 Base64 编码的原因很简单。Secret 的条目可以涵盖二进制数据,而不仅仅是纯文本。Base64编码可以将二进制数据转换为纯文本,以 YAML 或 JSON 格式展示。

提示 Secret 甚至可以被用来存储非敏感二进制数据。不过值得注意的是,Secret的大小限于1MB。

string Data 字段介绍

由于并非所有的敏感数据都是二进制形式,Kubernetes 允许通过 Secret 的 stringData 字段设置条目的纯文本值,如下面的代码清单所示。

kind: Secret
spiVersion: v1
stringData:
  foo: plain text
data:
  https.cert: xxx...
  https.key:  xxx...

stringData 字段是只写的(注意:是只写,非只读),可以被用来设置条目值。通过kubectl get -o yaml 获取 Secret 的 YAML 格式定义时,不会显示 stringData 字段。相反,stringData 字段中的所有条目(如上面示例中的 foo 条目) 会被 Base64 编码之后展示在 data 字段下。

在 pod 中读取 Secret 条目

通过 secret 卷将 Secret 暴露给容器之后,Secret 条目的值会被解码并以真实形式(纯文本或二进制)写入对应的文件。通过环境变量暴露 Secret 条目亦是如此。在这两种情况下,应用程序均无须主动解码,可直接读取文件内容或者查找环境变量。

在 pod 中使用 Secret

现在假设有一个 fortune-https Secret 已经包含了证书与密钥文件,接下来需要做的是配置 Nginx 服务器去使用它们。

修改 fortune-config ConfigMap 以开启 HTTPS

...
data:
  my-nginx-config.conf: |
    server {
      listen                80;
      listen                443 ssl;
      server_name           www.kubia-example.com;
      ssl_certificate       certs/https.cert;        // /etc/nginx 的相对位置
      ssl_certificate_key   certs/https.key;         // /etc/nginx 的相对位置
      ...
      
      location / {
        root   /user/share/nginx/html;
        index  index.html index.html;
      }
    }
  sleep-interval: |
 ......

上面配置了服务器从 /etc/nginx/certs 中读取证书与密钥文件,因此之后需要将 secret 卷挂载于此。

注意 所有条目第一行最后的管道符号表示后续的条目值是多行字面量。

挂载 fortune secret 至 pod

接下来需要创建一个新的 fortune-ttps pod,将含有证书与密钥的 secret 卷挂载至 pod 中的 web-server 容器,如下面的代码清单所示

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
  - image: some/image:env
    name: html-generator
    env:
    - name: INTERVAL
      valueFrom:
        configMapRef:
          name: fortune-config
          key: sleep-interval
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /user/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
    - name: certs                   // 配置 Nginx  /etc/nginx/certs 中读取证书和私钥,需要将 secret 卷挂载于此
      mountPath: /etc/nginx/certs
      readOnly: true
      ports:
      - containerPort: 80
      - containerPort: 443
    volumes:
    - name: html
      emptyDir: {}
    - name: config
      configMap:
        name: fortune-config
        items:
        - key: my-nginx-config.conf
          path: https.conf
    - name: certs                        // 这里引用 fortune-https Secret 来定义 secret 
      secret:
        secretName: fortune-https

注意与 configMap 卷相同,secret 卷同样支持通过 defaultModes 属性指定卷中文件的默认权限。

测试 Nginx 是否正使用 Secret 中的证书与密钥

pod 运行之后,开启端口转发隧道将 HTTPS 流量转发至 pod 443 端口,并用 curl 向服务器发送请求。

$ kubectl port-forward fortune-https 8443:443
$ curl https://localhost:8443 -k

若服务器配置正确,会得到一个响应,检查响应中服务器证书是否与之前生成的证书匹配。 curl 命令添加选项 -v 开启详细日志,如下面的代码清单所示。

$ curl https://localhost:8443 -k -v

Secret 卷存储于内存

通过挂载 secret 卷至文件夹 /etc/nginx/certs 将证书与私钥成功传递给容器。secret 卷采用内存文件系统列出容器的挂载点,由于存储在 Secret 中的数据不会写入磁盘,这样就无法被窃取。

通过环境变量暴露 Secret 条目

除卷之外,Secret 的独立条目可作为环境变量被暴露,就像 ConfigMap 中 sleep-interval 条目做的那样。举个例子,若想将 Secret 中的键 foo 暴露为环境变量 FOO_SECRET,需要在容器定义中添加如下片段.

env:
- name: FOO_SECRET
  valueFrom:
    secretKeyRef:          // 通过 Secret 条目设置环境变量
      name: fortune-https  // Secret 的键
      key: foo             // Secret 的名称

kubernetes 允许通过环境变量暴露 Secret,然而此特性的使用往往不是一个好主意。应用程序通常会在错误报告时转储环境变量,或者是启动时打印在应用日志中,无意中暴露了 Secret 信息。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,你并不知道它使用敏感数据做了什么。

提示 由于敏感数据可能在无意中被暴露,通过环境变量暴露 Secret 给容器之前请再三思考。为了确保安全性,请始终采用 secret 卷的方式暴露 Secret

了解镜像拉取 Secret

你己经学会了如何传递 Secret 给应用程序并使用它们包含的数据。 kubernetes 自身在有些时候希望我们能够传递证书给它,比如从某个私有镜像仓库拉取镜像时。这一点同样需通过 Secret 来做到。

到目前为止所使用的容器镜像均存储在公共仓库,从上面拉取镜像时无须任何特殊的证书。不过大部分组织机构不希望它们的镜像开放给所有人,因此会使用私有镜像仓库。部署 pod 时,如果容器镜像位于私有仓库,kubernetes 需拥有拉取镜像所需的证书。

小结

ConfigMap 和 Secret 配置应用程序