第 4 章 配置、Secrets 和 RBAC

容器的可组合性允许我们作为操作人员在运行时将配置数据引入到容器中。这使我们能够将应用程序的功能与它运行的环境解耦。通过容器运行时允许的在运行时传递环境变量或将外部卷装载到容器的约定,您可以在应用程序实例化时有效地更改应用程序的配置。作为开发人员,请务必考虑这种行为的动态性质,并允许使用环境变量或从应用程序运行时用户可用的特定路径读取配置数据是很重要的。

将敏感数据(如密码)移动到本机 Kubernetes API 对象时,了解 Kubernetes 如何保护对 API 的访问是非常重要的。Kubernetes 中最常用的实现安全方法是基于角色的访问控制 (RBAC),它实现了针对特定用户或组可以针对 API 采取的操作的细粒度权限结构。本章介绍有关 RBAC 的一些最佳实践,并提供了一些入门知识。

通过Configmaps和Secrets进行配置

Kubernetes 允许您通过 ConfigMaps 或Secret资源向我们的应用程序提供配置信息。两者之间的主要区别是 pod 存储接收信息的方式以及 etcd 数据存储中的数据存储方式。

ConfigMaps

应用程序通过某种类型的机制(如命令行参数、环境变量或系统可用的文件)使用配置信息是很常见的。容器允许开发人员将此配置信息与应用程序解耦,从而允许实现真正的应用程序可移植性。ConfigMap API 允许注入提供的配置信息。ConfigMaps 非常适合应用程序的需求,可以提供键/值对或复杂的批量数据(如 JSON、XML 或专用配置数据)。

ConfigMaps 不仅为 Pod 提供配置信息,还可以为更复杂的系统服务(如控制器、CRD、运算等)提供信息。如前所述,ConfigMap API 更多地是针对那些不是真正敏感数据的字符串数据。如果应用程序需要更敏感的数据,则Secret API 更合适。

对于应用程序使用 ConfigMap 数据,可以将它作为pod的卷或环境变量注入。

Secrets

要使用 ConfigMap 的许多属性和原因都应用于Secrets。主要区别在于Secret的基本性质。秘密数据应该以一种容易隐藏的方式存储和处理,并且如果环境是这样配置的,可能还会在休息时进行加密。秘密数据以base64编码的信息表示,关键是要理解这不是加密的。一旦秘密被注入到pod中,pod本身就可以以纯文本的形式查看秘密数据。

Secret 数据是指少量数据,默认情况下 Kubernetes 中的大小限制为 1 MB,用于 base64 编码的数据,因此由于编码的开销,请确保实际数据约为 750 KB。Kubernetes 有三种类型的Secrets:

  • generic

    这通常只是从文件、目录或使用 --from-literal= 参数从字符串文本创建的常规键/值对,如下所示:

    1kubectl create secret generic mysecret --from-literal=key1=$3cr3t1 --from-literal=key2=@3cr3t2
    
  • docker-registry

    如果有一个imagePullsecret来提供向私有 Docker 仓库身份验证所需的凭据,则 kubelet 在 pod 模板中传递时使用:

    1kubectl create secret docker-registry registryKey --docker-server myreg.azurecr.io --docker-username myreg --docker-password $up3r$3cr3tP@ssw0rd --docker-email ignore@dummy.com
    
  • tls

    这将从有效的公钥/私钥对创建传输层安全 (TLS) 密钥。只要证书采用有效的 PEM 格式,密钥对就会编码为secret,并可以传递到 pod ,以满足 SSL/TLS 需求:

    1kubectl create secret tls www-tls --key=./path_to_key/wwwtls.key --cert=./path_to_crt/wwwtls.crt
    

Secrets也仅安装在具有需要secret 的 pod 的节点上挂载到tmpfs中,并在需要该secret的 Pod 消失时将被删除。这样可以防止任何 secret 留在节点的磁盘上。尽管这看起来似乎是安全的,但请务必知道,默认情况下,机密以纯文本形式存储在 Kubernetes 的 etcd 数据存储中,并且系统管理员或云服务提供商必须努力确保etcd 环境,包括 etcd 节点之间的 mTLS,以及为 etcd 数据启用静态加密。Kubernetes 的最新版本使用 etcd3,并能够启用 etcd 本机加密;但是,这是一个手动过程,必须在 API 服务器配置中配置,为此指定提供程序和适当的密钥媒体,以正确加密 etcd 中保持的secret数据。自 Kubernetes v1.10(已在 v1.12 中升级为 beta)起,我们有 KMS 提供商,它承诺通过使用第三方 KMS 系统来保存正确的密钥,从而提供更安全的密钥流程。

ConfigMap和Secrets APIs 的最佳实践

使用 ConfigMap 或secret产生的大多数问题都是关于更新对象持有的数据时如何处理更改的错误假设。通过了解道路规则并添加一些技巧,以便更容易地遵守这些规则,您可以避免麻烦:

  • 要支持对应用程序进行动态更改,而无需重新部署新版本的 Pod,请将 ConfigMaps/Secrets 装载为卷,并使用文件监视器将应用程序进行配置,以检测更改的文件数据并根据需要重新配置自身。下面的代码显示将ConfigMap和Secret文件装载为卷的部署:
 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4    name: nginx-http-config
 5    namespace: myapp-prod
 6data:
 7  config: |
 8    http {
 9      server {
10        location / {
11        root /data/html;
12        }
13
14        location /images/ {
15          root /data;
16        }
17      }
18    }
1apiVersion: v1
2kind: Secret
3metadata:
4  name: myapp-api-key
5type: Opaque
6data:
7  myapikey: YWRtd5thSaW4=
 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: mywebapp
 5  namespace: myapp-prod
 6spec:
 7  containers:
 8  - name: nginx
 9    image: nginx
10    ports:
11    - containerPort: 8080
12    volumeMounts:
13    - mountPath: /etc/nginx
14      name: nginx-config
15    - mountPath: /usr/var/nginx/html/keys
16      name: api-key
17  volumes:
18    - name: nginx-config
19      configMap:
20        name: nginx-http-config
21        items:
22        - key: config
23          path: nginx.conf
24    - name: api-key
25      secret:
26        name: myapp-api-key
27        secretname: myapikey

注意

使用 volumeMounts 时需要考虑几件事。首先,一旦创建 ConfigMap/Secret,就将其添加为 Pod 规范中的卷。然后,将该卷装入容器的文件系统中。ConfigMap/Secret中的每个属性名称将成为装载目录中的新文件,每个文件的内容将是ConfigMap/Secret中指定的值。其次,避免使用 volumeMounts.subPath 属性安装ConfigMap/Secret。如果使用新数据更新 ConfigMap/Secret,这将阻止数据在卷中动态更新。


  • ConfigMap/Secrets必须存在于将消耗这些容器的 pod 的名称空间中,如果ConfigMap/Secret不存在,可选标志可用于防止pod不启动。
  • 使用准入控制器确保特定的配置数据,或防止未设置特定配置值的部署。例如,如果要求所有生产 Java 工作负载在生产环境中设置某些 JVM 属性。有一个称为 Alpha API 的PodPresets API,它允许基于注释将 ConfigMaps 和Secret应用于所有 Pod,而无需编写自定义准入控制器。
  • 如果使用 Helm 将应用程序发布到您的环境中,则可以使用一个生命周期挂钩来确保在应用部署之前部署ConfigMap/Secret模板。
  • 一些应用程序要求其配置作为单个文件(如 JSON 或 YAML 文件)应用。ConfigMap/Secret允许使用|符号处理整个原始数据块,如下所示:
 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4  name: config-file
 5data:
 6  config: |
 7    {
 8      "iotDevice": {
 9        "name": "remoteValve",
10        "username": "CC:22:3D:E3:CE:30",
11        "port": 51826,
12        "pin": "031-45-154"
13      }
14    }
  • 如果应用程序使用系统环境变量来确定其配置,则可以使用 ConfigMap 数据的注入创建映射到 pod 的环境变量。有两种主要方法可以做到这一点:使用 envFrom 或 将 ConfigMap 中的每个键/值对作为一系列环境变量挂载到 pod 中,然后使用 configMapRefsecretRef 分配各个键,或使用 configMapKeyRefsecretKeyRef 分配各自的值。
  • 如果您正在使用 configMapKeyRefsecretKeyRef 方法,请注意,如果实际键不存在,这将阻止 pod 启动。
  • 如果使用 envFrom 将ConfigMap/Secret的所有键/值对加载到pod中,那么任何被认为是无效的环境值的键都将被跳过,但是,将pod允许启动;如果pod 的事件将有一个带有 InvalidVariableNames 原因的事件,以及关于跳过哪个键的适当消息。 下面的代码是一个使用ConfigMap和Secret引用作为环境变量的部署的示例:
1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: mysql-config
5data:
6  mysqldb: myappdb1
7  user: mysqluser1
1apiVersion: v1
2kind: Secret
3metadata:
4  name: mysql-secret
5type: Opaque
6data:
7  rootpassword: YWRtJasdhaW4=
8  userpassword: MWYyZDigKJGUyfgKJBmU2N2Rm
 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: myapp-db-deploy
 5spec:
 6  selector:
 7    matchLabels:
 8      app: myapp-db
 9  template:
10    metadata:
11      labels:
12        app: myapp-db
13    spec:
14      containers:
15      - name: myapp-db-instance
16        image: mysql
17        resources:
18          limits:
19            memory: "128Mi"
20            cpu: "500m"
21        ports:
22        - containerPort: 3306
23        env:
24          - name: MYSQL_ROOT_PASSWORD
25            valueFrom:
26              secretKeyRef:
27                name: mysql-secret
28                key: rootpassword
29          - name: MYSQL_PASSWORD
30            valueFrom:
31              secretKeyRef:
32                name: mysql-secret
33                key: userpassword
34          - name: MYSQL_USER
35            valueFrom:
36              configMapKeyRef:
37                name: mysql-config
38                key: user
39          - name: MYSQL_DB
40            valueFrom:
41              configMapKeyRef:
42                name: mysql-config
43                key: mysqldb
  • 如果需要向容器传递命令行参数,可以使用$(ENV_KEY)插值表达式获取环境变量数据:
 1[...]
 2spec:
 3  containers:
 4  - name: load-gen
 5    image: busybox
 6    command: ["/bin/sh"]
 7args: ["-c", "while true; do curl $(WEB_UI_URL); sleep 10;done"]
 8    ports:
 9    - containerPort: 8080
10    env:
11    - name: WEB_UI_URL
12      valueFrom:
13        configMapKeyRef:
14          name: load-gen-config
15          key: url
  • 当使用 ConfigMap/Secret 数据作为环境变量时,请务必了解,在 Pod 中对数据的更新不会在Pod 中更新,并且需要通过删除pod和让ReplicaSet控制器创建新的 pod 或触发部署更新来重新启动pod,这将遵循部署规范中声明的正确应用程序更新策略。
  • 我们更容易假设对 ConfigMap/Secret 的所有更改都需要对整个部署进行更新;这可确保即使您使用环境变量或卷,代码也会采用新的配置数据。为了简化这一点,可以使用 CI/CD 管道更新 ConfigMap/Secret 的 name 属性,并更新部署中的引用,然后通过部署的正常 Kubernetes 更新策略触发更新。我们将在以下示例代码中探讨这一点。如果使用 Helm 将应用程序代码部署到 Kubernetes 中,则可以利用部署模板中的注解来检查 ConfigMap/Secret的sha256 校验和。这将触发 Helm 在ConfigMap/Secret 中的数据发生更改时使用 helm upgrade 命令更新部署:
1apiVersion: apps/v1
2kind: Deployment
3[...]
4spec:
5  template:
6    metadata:
7      annotations:
8        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
9[...]

针对Secret的最佳实践

由于Secret API 敏感数据的性质,自然有更具体的最佳实践,这些最佳实践主要围绕数据本身的安全性:

  • Secret API 的原始规范概述了一个可插拔体系结构,允许Secret 的实际存储根据需求进行配置。HashiCorp Vault、Aqua Security、Twistlock、AWS secret Manager、谷歌Cloud KMS或Azure Key Vault等解决方案允许使用外部存储系统来存储Secret数据,使用的加密和审核级别比Kubernetes提供的更高。
  • imagePullSecrets 分配给 pod 将使用 serviceaccount 来自动装载Secret,而无需在 pod.spec 中声明它。您可以修补应用程序名称空间的默认服务帐户,并将 imagePullSecrets 直接添加到其中。这将自动将其添加到命名空间中的所有 Pod:
1# Create the docker-registry secret first
2kubectl create secret docker-registry registryKey --docker-server \
3myreg.azurecr.io --docker-username myreg --docker-password \
4$up3r$3cr3tP@ssw0rd --docker-email ignore@dummy.com
5
6# patch the default serviceaccount for the namespace you wish to configure
7kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name":
8"registryKey"}]}'
  • 使用 CI/CD 功能在发布管道期间使用硬件安全模块 (HSM) 从安全保管库或加密存储获取Secret。这允许职责的分离。安全管理团队可以创建和加密Secret,而开发人员只需引用预期Secret的名称即可。这也是首选 DevOps 流程,以确保更动态的应用程序交付流程。

RBAC

在大型分布式环境中工作时,通常需要某种安全机制来防止对关键系统的未授权访问。关于如何限制对计算机系统中资源访问有许多策略,但大多数都经历相同的阶段。使用类似于飞往外国这样的普通经验可以帮助解释在像Kubernetes这样的系统中发生的过程。我们可以使用普通旅客的护照、旅行签证、海关或边防警卫的经验来展示这个过程:

  1. 护照(主体认证)。通常你需要有一本由政府机构签发的护照,该机构会对你的身份进行某种验证。这将相当于 Kubernetes 的用户帐户。Kubernetes 依靠外部权威机构对用户进行身份验证;但是,服务帐户是一种由 Kubernetes 直接管理的帐户类型。
  2. 签证或旅行政策(授权)。各国将签订正式协议,通过签证等正式短期协议接受持有其他国家护照的旅客。根据签证的具体类型,签证还会列出访问者可以做什么以及他们可以在访问国家停留多长时间。这相当于在 Kubernetes 的授权。Kubernetes 有不同的授权方法,但最常用的是 RBAC。这允许非常精细地访问不同的 API 功能。
  3. 边境巡逻或海关(入境管制)。在进入外国时,通常有一个权威机构来检查必要的文件,包括护照和签证,在许多情况下,还会检查被带入该国的东西,以确保它遵守该国的法律。在 Kubernetes 中,这相当于准入控制器。准入控制器可以根据定义的规则和策略允许、拒绝或将请求更改为 API。Kubernetes 有许多内置的准入控制器,如 PodSecurity、ResourceQuota和ServiceAccount控制器。Kubernetes 还允许通过使用验证或修改允许控制器来实现动态控制器。

本节的重点是对这三个领域中最不容易理解和避免的:RBAC。在概述一些最佳实践之前,我们首先必须介绍 Kubernetes RBAC 的入门知识。

RBAC入门

Kubernetes 中的 RBAC 流程有三个需要定义的主要组件:subject、rule和role binding。

SUBJECTS

第一个组件是subject,即实际上正在检查访问的项。subject通常是用户、服务帐户或组。如前所述,使用的授权模块在 Kubernetes 之外处理用户和组。我们可以将它们分类为基本身份验证、x.509 客户端证书或承载令牌。最常见的实现使用 x.509 客户端证书或某种类型的承载令牌,使用诸如 OpenID 连接系统(如 Azure 活动目录 (Azure AD)、Salesforce 或 Google 之类的内容。


注意

Kubernetes 中的服务帐户与用户帐户不同,因为它们受命名空间绑定,内部存储在 Kubernetes 中;它们旨在表示进程,而不是人员,并由本机 Kubernetes 控制器管理。


RULES

简单地说,这是可以对 API 中的特定对象(资源)或一组对象执行的实际列表。Verbs 与典型的 CRUD(创建、读取、更新和删除)类型操作对齐,但在 Kubernetes 中添加了一些功能,如watchlistexec。这些对象与不同的 API 组件对齐,并按类别分组。例如,Pod 对象是核心 API 的一部分,可以用apiGroup: ""引用,而Deployment 在app API 组下。这是 RBAC 流程的真正功能,可能也是在创建适当的 RBAC 控制时使人们感到害怕和迷惑的原因。

ROLES

角色允许定义所定义的规则的范围。Kubernetes 有两种类型的角色,roleclusterRole,区别在于role 是特定于名称空间的,而clusterRole是跨所有名称空间的群集范围的角色。具有名称空间作用域的角色定义示例如下所示:

1kind: Role
2apiVersion: rbac.authorization.k8s.io/v1
3metadata:
4  namespace: default
5  name: pod-viewer
6rules:
7- apiGroups: [""] # "" indicates the core API group
8  resources: ["pods"]
9  verbs: ["get", "watch", "list"]

ROLEBINDINGS

角色绑定允许将subject (如用户或组)映射到特定role。绑定也有两种模式:roleBinding用于特定的名称空间,而clusterRoleBinding 它跨越整个群集。下面是一个使用名称空间作用域的 RoleBinding 示例:

 1kind: RoleBinding
 2apiVersion: rbac.authorization.k8s.io/v1
 3metadata:
 4  name: noc-helpdesk-view
 5  namespace: default
 6subjects:
 7- kind: User
 8  name: helpdeskuser@example.com
 9  apiGroup: rbac.authorization.k8s.io
10roleRef:
11  kind: Role #this must be Role or ClusterRole
12  name: pod-viewer # this must match the name of the Role or ClusterRole to bind to
13  apiGroup: rbac.authorization.k8s.io

RBAC 最佳实践

RBAC 是运行安全、可靠和稳定的 Kubernetes 环境的关键组件。RBAC 背后的概念可能很复杂;但是,遵循一些最佳实践可以缓解一些主要的障碍:

  • 在 Kubernetes 中运行的应用程序很少需要与其关联的 RBAC 角色和角色绑定。只有当应用程序代码实际上直接与 Kubernetes API 进行交互时,应用程序才需要 配置 RBAC。
  • 如果应用程序确实需要直接访问 Kubernetes API 可能更改配置,具体取决于要添加到服务的终结点,或者如果它需要列出特定名称空间中的所有 pod,则最佳做法是创建一个新的ServiceAccount,然后在pod中指定。最后,创建一个具有完成其目标所需的最少权限的角色。
  • 使用 OpenID 连接服务,该服务支持身份管理,如果需要,还需要双重身份验证。这将允许更高级别的身份验证。将用户组映射到具有完成作业所需的最少权限的角色。
  • 除了前面提到的实践之外,您应该使用Just in Time (JIT) 访问系统,以便允许站点可靠性工程师 (SREs) 以及那些可能需要在短时间内升级特权来完成非常具体任务的人员。或者,这些用户应具有不同的身份,这些身份应经过更严格地登录审核,并且这些帐户应具有绑定到角色的用户帐户或组分配的更高权限。
  • 特定服务帐户应用于部署到 Kubernetes 群集的 CI/CD 工具。这可确保群集内的可审核性,并了解谁可能部署或删除了群集中的任何对象。
  • 如果使用 Helm 部署应用程序,则默认服务帐户为 Tiller,已部署到 kube-system。最好将 Tiller 部署到每个名称空间,使用专门针对该名称空间的 Tiller 的服务帐户。在调用 Helm install/upgrade 命令的 CI/CD 工具中,作为预备步骤,使用服务帐户和部署的特定名称空间初始化 Helm 客户端。每个名称空间的服务帐户名称可以相同,但名称空间应是特定的。需要指出的是,截至本书出版时,Helm v3 处于 alpha 状态,其核心原则之一是 Tiller 不再需要在群集中运行。使用服务帐户和命名空间的 Helm Init 示例如下所示:
 1kubectl create namespace myapp-prod
 2
 3kubectl create serviceaccount tiller --namespace myapp-prod
 4
 5cat  <<EOF | kubectl apply -f -
 6kind: Role
 7apiVersion: rbac.authorization.k8s.io/v1
 8metadata:
 9  name: tiller
10  namespace: myapp-prod
11rules:
12- apiGroups: ["", "batch", "extensions", "apps"]
13  resources: ["*"]
14  verbs: ["*"]
15EOF
16
17cat <<EOF | kubectl apply -f -
18kind: RoleBinding
19apiVersion: rbac.authorization.k8s.io/v1
20metadata:
21  name: tiller-binding
22  namespace: myapp-prod
23subjects:
24- kind: ServiceAccount
25  name: tiller
26  namespace: myapp-prod
27roleRef:
28  kind: Role
29  name: tiller
30  apiGroup: rbac.authorization.k8s.io
31  EOF
32
33helm init --service-account=tiller --tiller-namespace=myapp-prod
34
35helm install ./myChart --name myApp --namespace myapp-prod --set global.namespace=myapp-prod

注意

某些公共 Helm chart没有用于部署应用程序组件的名称空间选项的值条目。这可能需要直接自定义 Helm chart或使用可部署到任何名称空间并有权创建名称空间的提升的 Tiller 帐户。


  • 限制需要 watchlist 在Secret API 上使用的任何应用程序。这基本上允许应用程序或部署 pod 的人员查看该名称空间中的秘密。如果应用程序需要访问特定机密的Secret API,请限制使用get应用程序需要读取的任何特定机密,而直接分配这些机密。

总结

为云原生交付开发应用程序的原则是另一个的主题,但是普遍认为,严格地将配置和代码分离是成功的关键。对于非敏感数据的原生对象、ConfigMap API 和敏感数据(Secret API),Kubernetes 现在可以采用声明性方法管理此过程。随着越来越多的关键数据在 Kubernetes API 中以原生方式表示和存储,通过适当的封闭安全过程(如 RBAC 和集成身份验证系统)保护对这些 API 的访问至关重要。

正如您将在本书的其余部分中看到的,这些原则渗透到将服务正确部署到 Kubernetes 平台的各个方面,以构建一个稳定、可靠、安全和强大的系统。