Kubernetes 通过 Rest 加密 Secret 数据

本文将展示如何通过 rest 启用和配置 secret 数据的加密。

Before you begin

  • You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using Minikube, or you can use one of these Kubernetes playgrounds:
  • Katacoda
  • Play with Kubernetes

To check the version, enter kubectl version.

  • 需要 Kubernetes 1.7.0 或者更高版本
  • 需要 etcd v3 或者更高版本
  • 通过 rest 加密在 1.7.0 中仍然是 alpha 版本,这意味着它可能会在没有通知的情况下进行更改。在升级到 1.8.0 之前,用户可能需要解密他们的数据。

配置并确定是否已启用通过 rest 加密

kube-apiserver 的参数 --experimental-encryption-provider-config 控制 API 数据在 etcd 中的加密方式。 下面提供一个配置示例。

理解 rest 配置的加密

kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
    - secrets
    providers:
    - identity: {}
    - aesgcm:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - aescbc:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - secretbox:
        keys:
        - name: key1
          secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=

每个 resources 数组项目是一个单独的完整的配置。 resources.resources 字段是要加密的 Kubernetes 资源名称(resource 或 resource.group)的数组。 providers 数组是可能的加密 provider 的有序列表。每个条目只能指定一个 provider 类型(可以是 identity 或 aescbc,但不能在同一个项目中同时指定)。

列表中的第一个提供者用于加密进入存储的资源。当从存储器读取资源时,与存储的数据匹配的所有提供者将尝试按顺序解密数据。 如果由于格式或密钥不匹配而导致提供者无法读取存储的数据,则会返回一个错误,以防止客户端访问该资源。

重要: 如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从基础 etcd 中删除该密钥。任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。

Providers:

名称 加密类型 强度 速度 密钥长度 其它事项
identity N/A N/A N/A 不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。
aescbc 填充 PKCS#7 的 AES-CBC 最强 32字节 建议使用的加密项,但可能比 secretbox 稍微慢一些。
secretbox XSalsa20 和 Poly1305 更快 32字节 较新的标准,在需要高度评审的环境中可能不被接受。
aesgcm 带有随机数的 AES-GCM 必须每 200k 写入一次 最快 16, 24, 或者 32字节 建议不要使用,除非实施了自动密钥循环方案。

每个 provider 都支持多个密钥 - 在解密时会按顺序使用密钥,如果是第一个 provider,则第一个密钥用于加密。

加密您的数据

创建一个新的加密配置文件:

kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <BASE 64 ENCODED SECRET>
    - identity: {}

遵循如下步骤来创建一个新的 secret:

  1. 生成一个 32 字节的随机密钥并进行 base64 编码。如果您在 Linux 或 Mac OS X 上,请运行以下命令:
     head -c 32 /dev/urandom | base64
    
  2. 将这个值放入到 secret 字段中。
  3. 设置 kube-apiserver 的 --experimental-encryption-provider-config 参数,将其指定到配置文件所在位置。
  4. 重启您的 API server。

重要: 您的配置文件包含可以解密 etcd 内容的密钥,因此您必须正确限制主设备的权限,以便只有能运行 kube-apiserver 的用户才能读取它。

验证数据是否被加密

数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver 后,任何新创建或更新的密码在存储时都应该被加密。如果想要检查,你可以使用 etcdctl 命令行程序来检索你的加密内容。

  1. 创建一个新的 secret,名称为 secret1,命名空间为 default:
     kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  2. 使用 etcdctl 命令行,从 etcd 中读取 secret:
     ETCDCTL_API=3 etcdctl get /kubernetes.io/secrets/default/secret1 [...] | hexdump -C
    

    这里的 [...] 是用来连接 etcd 服务的额外参数。

  3. 验证存储的密钥前缀是否为 k8s:enc:aescbc:v1:,这表明 aescbc provider 已加密结果数据。
  4. 通过 API 检索,验证 secret 是否被正确解密:
     kubectl describe secret secret1 -n default
    

    必须匹配 mykey: mydata

确保所有 secret 都被加密

由于 secret 是在写入时被加密,因此对 secret 执行更新也会加密该内容。

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

上面的命令读取所有 secret,然后使用服务端加密来进行更新。 如果由于冲突写入而发生错误,请重试该命令。 对于较大的集群,您可能希望通过命名空间或更新脚本来分割 secret。

回滚解密密钥

在不发生停机的情况下更改 secret 需要多步操作,特别是在有多个 kube-apiserver 进程正在运行的高可用部署的情况下。

  1. 生成一个新密钥并将其添加为所有服务器上当前提供程序的第二个密钥条目
  2. 重新启动所有 kube-apiserver 进程以确保每台服务器都可以使用新密钥进行解密
  3. 将新密钥设置为 keys 数组中的第一个条目,以便在配置中使用其进行加密
  4. 重新启动所有 kube-apiserver 进程以确保每个服务器现在都使用新密钥进行加密
  5. 运行 kubectl get secrets --all-namespaces -o json | kubectl replace -f - 以用新密钥加密所有现有的秘密
  6. 在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥

如果只有一个 kube-apiserver,第 2 步可能可以忽略。

解密所有数据

要禁用 rest 加密,请将 identity provider 作为配置中的第一个条目:

kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
    - secrets
    providers:
    - identity: {}
    - aescbc:
        keys:
        - name: key1
          secret: <BASE 64 ENCODED SECRET>

并重新启动所有 kube-apiserver 进程。然后运行命令 kubectl get secrets --all-namespaces -o json | kubectl replace -f - 强制解密所有 secret。

译者:tianshapjq / 原文链接

K8S中文社区微信公众号

kubernetes 通过环境变量向容器暴露 Pod 信息

本页展示了 Pod 如何使用环境变量向 Pod 中运行的容器暴露有关自身的信息。环境变量可以暴露 Pod 字段和容器字段。

Before you begin

You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using Minikube, or you can use one of these Kubernetes playgrounds:

To check the version, enter kubectl version.

Downward API

有两种方式将 Pod 和容器字段暴露给运行中的容器:

这两种暴露 Pod 和容器字段的方式被称为 Downward API。

使用 Pod 字段作为环境变量的值

在本练习中,您将创建一个有一个容器的 Pod。下面是POD的配置文件:

dapi-envars-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-fieldref
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
          printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
          sleep 10;
        done;
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: MY_POD_SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
  restartPolicy: Never

在配置文件中,您可以看到五个环境变量。env 字段是 EnvVars 的数组。数组中的第一个元素指定 MY_NODE_NAME 环境变量从 Pod 的 spec.nodeName 字段中获取其值。类似地,其他环境变量从 Pod 字段中获取它们的名称。

注意: 示例中的字段是 Pod 的字段。它们不是 Pod 中的容器的字段。

创建 Pod:

kubectl create -f https://k8s.io/docs/tasks/inject-data-application/dapi-envars-pod.yaml

验证 Pod 中的容器是 running 状态:

kubectl get pods

查看容器日志:

kubectl logs dapi-envars-fieldref

输出显示选定的环境变量的值:

minikube
dapi-envars-fieldref
default
172.17.0.4
default

想要知道为什么这些值会打印在日志中,请查看配置文件的 command 和 args 字段。当容器启动时,它将 5 个环境变量的值写到标准输出中。每十秒钟重复一次。

接下来,将一个 shell 放入正在您的 Pod 中运行的容器里面:

kubectl exec -it dapi-envars-fieldref -- sh

在 shell 中,查看环境变量:

/# printenv

输出结果显示,某些环境变量已被指定为 Pod 字段的值:

MY_POD_SERVICE_ACCOUNT=default
...
MY_POD_NAMESPACE=default
MY_POD_IP=172.17.0.4
...
MY_NODE_NAME=minikube
...
MY_POD_NAME=dapi-envars-fieldref

使用容器字段作为环境变量的值

在前面的练习中,您使用 Pod 字段作为环境变量的值。在下一个练习中,您将使用容器字段用作环境变量的值。下面是一个 Pod 的配置文件,其中包含一个容器:

dapi-envars-container.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-resourcefieldref
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox:1.24
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_CPU_REQUEST MY_CPU_LIMIT;
          printenv MY_MEM_REQUEST MY_MEM_LIMIT;
          sleep 10;
        done;
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      env:
        - name: MY_CPU_REQUEST
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: requests.cpu
        - name: MY_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: limits.cpu
        - name: MY_MEM_REQUEST
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: requests.memory
        - name: MY_MEM_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: limits.memory
  restartPolicy: Never

在配置文件中,您可以看到四个环境变量。env 字段是 EnvVars 的数组。数组中的第一个元素指定 MY_CPU_REQUEST 环境变量从名为 test-container 的容器的 requests.cpu 字段中获取其值。类似地,其他环境变量从容器字段中获取它们的值。

创建 Pod:

kubectl create -f https://k8s.io/docs/tasks/inject-data-application/dapi-envars-container.yaml

验证 Pod 中的容器是 running 状态:

kubectl get pods

查看容器日志:

kubectl logs dapi-envars-resourcefieldref

输出展示了选定环境变量的值:

1
1
33554432
67108864

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

kubeadm 实现细节

kubeadm init 和 kubeadm join 为从头开始创建一个 Kubernetes 集群的最佳实践共同提供了一个很好的用户体验。但是,kubeadm 如何 做到这一点可能并不明显。

本文档提供了有关发生了什么事情的更多详细信息,旨在分享关于 Kubernetes 集群最佳实践的知识。

核心设计原则

使用 kubeadm init 和 kubeadm join 设置的集群应该:

  • 安全:
    • 它应该采用最新的最佳做法,如:
      • 强制实施 RBAC
      • 使用节点授权器
      • 控制平面组件之间使用安全通信
      • API​​ server 和 kubelet 之间使用安全通信
      • 锁定 kubelet API
      • 锁定对系统组件(如 kube-proxy 和 kube-dns)的 API 访问权限
      • 锁定引导令牌可以访问的内容
      • 等等
  • 使用方便:
    • 用户只需运行几个命令即可:
      • kubeadm init
      • export KUBECONFIG=/etc/kubernetes/admin.conf
      • kubectl apply -f <network-of-choice.yaml>
      • kubeadm join --token <token> <master-ip>:<master-port>
  • 可扩展:
    • 例如,它 不 应该支持任何网络提供商,相反,配置网络应该是超出了它的范围
    • 应该提供使用配置文件自定义各种参数的可能性

常量和众所周知的值和路径

为了降低复杂性并简化 kubeadm 实施的部署解决方案的开发,kubeadm 使用一组有限的常量值,用于众所周知的路径和文件名。

Kubernetes 目录 /etc/kubernetes 在应用中是一个常量,因为它明显是大多数情况下的给定路径,也是最直观的位置; 其他常量路径和文件名是:

  • /etc/kubernetes/manifests 作为 kubelet 寻找静态 Pod 的路径。静态 Pod 清单的名称是:
    • etcd.yaml
    • kube-apiserver.yaml
    • kube-controller-manager.yaml
    • kube-scheduler.yaml
  • /etc/kubernetes/ 作为存储具有控制平面组件标识的 kubeconfig 文件的路径。kubeconfig 文件的名称是:
    • kubelet.conf (bootstrap-kubelet.conf - 在 TLS 引导期间)
    • controller-manager.conf
    • scheduler.conf
    • admin.conf 用于集群管理员和 kubeadm 本身
  • 证书和密钥文件的名称:
    • ca.crt,ca.key 为 Kubernetes 证书颁发机构
    • apiserver.crt,apiserver.key 用于 API server 证书
    • apiserver-kubelet-client.crt,apiserver-kubelet-client.key 用于由 API server 安全地连接到 kubelet 的客户端证书
    • sa.pub,sa.key 用于签署 ServiceAccount 时控制器管理器使用的密钥
    • front-proxy-ca.crt,front-proxy-ca.key 用于前台代理证书颁发机构
    • front-proxy-client.crt,front-proxy-client.key 用于前端代理客户端

kubeadm init 工作流程内部设计

kubeadm init 内部工作流程 由一系列要执行的原子工作任务组成,如 kubeadm init 所述。

kubeadm alpha phase 命令允许用户单独调用每个任务,并最终提供可重用和可组合的 API/工具箱,可供其他 Kubernetes 引导工具、任何 IT 自动化工具或高级用户创建自定义集群使用。

预检检查

Kubeadm 在启动 init 之前执行一组预检检查,目的是验证先决条件并避免常见的集群启动问题。在任何情况下,用户都可以使用 --ignore-preflight-errors 选项跳过特定的预检检查(或最终所有预检检查)。

  • [警告]如果要使用的 Kubernetes 版本(与 --kubernetes-version 标记一起指定)至少比 kubeadm CLI 版本高一个次要版本
  • Kubernetes 系统要求:
    • 如果在 Linux 上运行:
      • [错误] 如果不是 Kernel 3.10+ 或具有特定 KernelSpec 的 4+
      • [错误] 如果需要的 cgroups 子系统没有设置
    • 如果使用 docker:
      • [警告/错误] 如果 Docker 服务不存在,如果它被禁用,如果它不是 active 状态
      • [错误] 如果 Docker 端点不存在或不起作用
      • [警告] 如果 docker 版本 > 17.03
    • 如果使用其他 cri 引擎:
      • [错误] 如果 crictl 没有响应
  • [错误] 如果用户不是root用户
  • [错误] 如果机器主机名不是有效的 DNS 子域
  • [警告] 如果通过网络查找无法到达主机名
  • [错误] 如果 kubelet 版本低于 kubeadm 支持的最小 kubelet 版本(当前小版本 -1)
  • [错误] 如果 kubelet 版本至少比所需的控制平面版本更高一些(不受支持的版本)
  • [警告] 如果 kubelet 服务不存在或禁用
  • [警告] 如果 firewalld 处于活动状态
  • [错误] 如果 API​​ server 的 bindPort 或者 port 10250/10251/10252 已经被使用
  • [错误] 如果/etc/kubernetes/manifest 文件夹已经存在,并且非空
  • [错误] 如果 /proc/sys/net/bridge/bridge-nf-call-iptables 文件不存在或者不包含 1
  • [错误] 如果发布地址是 ipv6 并且 /proc/sys/net/bridge/bridge-nf-call-ip6tables 不存在或者不包含 1
  • [错误] 如果 swap 打开
  • [错误] 如果 ip、iptables、mount 或者 nsenter 命令没有出现在命令路径中
  • [警告] 如果 ebtables、ethtool、socat、tc、touch 和 crictl 命令没有出现在命令路径中
  • [警告] 如果 API server、Controller-manager、Scheduler 的额外参数中包含一些无效的选项
  • [警告] 如果连接到 https://API.AdvertiseAddress:API.BindPort 需要通过代理
  • [警告] 如果连接到服务子网需要通过代理(只检查第一个地址)
  • [警告] 如果连接到 pod 子网需要通过代理(只检查第一个地址)
  • 如果提供外部 etcd:
    • [错误] 如果 etcd 版本低于 3.0.14
    • [错误] 如果指定了 etcd 证书或密钥,但未提供
  • 如果不提供外部 etcd(因此将安装本地 etcd):
    • [错误] 如果使用端口 2379
    • [错误] 如果 Etcd.DataDir 文件夹已经存在并且不是空的
  • 如果授权模式是 ABAC:
    • [错误] 如果 abac_policy.json 不存在
  • 如果授权模式是 WebHook:
    • [错误] 如果 webhook_authz.conf 不存在

请注意:

  1. 预检检查可以通过 kubeadm alpha phase preflight 命令单独调用

生成必要的证书

Kubeadm 为不同目的生成证书和私钥对:

  • Kubernetes 集群的自签名证书颁发机构保存到 ca.crt 文件和 ca.key 私钥文件中
  • API server 的服务证书,使用 ca.crt 作为 CA 生成,并保存到 apiserver.crt 文件中,并带有其私钥 apiserver.key。此证书应包含以下其他名称:
    • Kubernetes 服务的内部 clusterIP(服务 CIDR 中的第一个地址,例如,如果服务子网是 10.96.0.0/12 则为 10.96.0.1)
    • Kubernetes DNS 名称,例如,如果 --service-dns-domain 标志的值为 cluster.local,则为 kubernetes.default.svc.cluster.local,再加上默认的 DNS 名称 kubernetes.default.svc、kubernetes.default 和 kubernetes
    • 节点名称
    • --apiserver-advertise-address
    • 由用户指定的其他替代名称
  • 用于 API server 的安全连接到 kubelet 的客户端证书,使用 ca.crt 作为 CA 生成并使用私钥 apiserver-kubelet-client.key 保存到文件 apiserver-kubelet-client.crt中。这个证书应该在 system:masters 组织中
  • 一个用于签名 ServiceAccount 令牌的私钥,该令牌与它的公钥 sa.pub 一起保存到 sa.key 文件中。
  • 前端代理的证书颁发机构保存到 front-proxy-ca.crt 文件中,其密钥为 front-proxy-ca.key
  • 前端代理客户端的客户证书,使用 front-proxy-ca.crt 作为 CA 生成,并使用其私钥 front-proxy-client.key 保存到 front-proxy-client.crt 文件中

证书默认存储在 /etc/kubernetes/pki 中,但该目录可使用 --cert-dir 标志进行配置。

请注意:

  1. 如果给定的证书和私钥对都存在,并且其内容评估符合上述规范,则将使用现有文件并跳过给定证书的生成阶段。这意味着用户可以将现有 CA 复制到 /etc/kubernetes/pki/ca.{crt,key},然后 kubeadm 将使用这些文件来签署剩余的证书。请参与 使用自定义证书
  2. 只有 CA 可以提供 ca.crt 文件,但不提供 ca.key 文件,如果所有其他证书和 kubeconfig 文件已就位,kubeadm 会识别此情况并激活 ExternalCA,这也意味着 controller-manager 中的 csrsigner 控制器将不会启动
  3. 如果 kubeadm 在 ExternalCA 模式下运行; 所有的证书都必须由用户提供,因为 kubeadm 本身不能生成它们
  4. 在 --dry-run 模式中执行 kubeadm 的情况下,证书文件被写入临时文件夹中
  5. 使用 kubeadm alpha phase certs all 命令可以单独调用证书生成动作

为控制平面组件生成 kubeconfig 文件

具有控制平面组件标识的 Kubeadm kubeconfig 文件:

  • kubelet 使用的 kubeconfig 文件:/etc/kubernetes/kubelet.conf; 在这个文件内嵌入一个具有 kubelet 身份的客户端证书。这个客户证书应该:
    • 在 system:nodes 组织中,符合 节点授权 模块的要求
    • 有 CN system:node:<hostname-lowercased>
  • controller-manager 使用的 kubeconfig 文件:/etc/kubernetes/controller-manager.conf; 在这个文件内嵌入一个带有 controller-manager 身份的客户端证书。此客户端证书应具有 CN system:kube-controller-manager,默认由 RBAC 核心组件角色 定义
  • scheduler 使用的 kubeconfig 文件:/etc/kubernetes/scheduler.conf; 在这个文件内嵌入一个带有 scheduler 标识的客户端证书。此客户端证书应具有 CN system:kube-scheduler,默认由 RBAC 核心组件角色 定义

此外,生成一个 kubeadm 去使用它自己以及管理员使用的 kubeconfig 文件,并保存到 /etc/kubernetes/admin.conf 文件中。这里的 “管理员” 定义了正在管理集群并希望完全控制(root)集群的实际人员。管理员的嵌入式客户端证书应该:

请注意:

  1. ca.crt 证书嵌入在所有 kubeconfig 文件中。
  2. 如果给定的 kubeconfig 文件存在,并且其内容的评估符合上述规范,则将使用现有文件,并跳过给定 kubeconfig 的生成阶段
  3. 如果 kubeadm 以 ExternalCA 模式运行,则所有必需的 kubeconfig 也必须由用户提供,因为 kubeadm 本身不能生成它们中的任何一个
  4. 如果在 --dry-run 模式下执行 kubeadm ,kubeconfig 文件将写入临时文件夹中
  5. 使用 kubeadm alpha phase kubeconfig all 命令可以单独调用 Kubeconfig 文件生成动作

为控制平面组件生成静态 Pod 清单

kubeadm 将控制平面组件的静态 Pod 清单文件写入 /etc/kubernetes/manifests; Kubelet 会监控这个目录,在启动时创建 pod。

静态 Pod 清单共享一组通用属性:

  • 所有静态 Pod 都部署在 kube-system 命名空间上
  • 所有静态 Pod 都可以获取 tier:control-plane 和 component:{component-name} 标记
  • 所有的静态 Pod 都会获得 scheduler.alpha.kubernetes.io/critical-pod 注解(这将转移到适当的解决方案,即在准备就绪时使用 pod 优先级和抢占)
  • 在所有静态 Pod 上设置 hostNetwork: true,以便在网络配置之前允许控制平面启动; 因此:
    • controller-manager 和 scheduler 使用来指代该 API server 的地址为 127.0.0.1
    • 如果使用本地 etcd 服务器,etcd-servers 地址将被设置为 127.0.0.1:2379
  • controller-manager 和 scheduler 均启用选举
  • controller-manager 和 scheduler 将引用 kubeconfig 文件及其各自的唯一标识
  • 所有静态 Pod 都会获得用户指定的额外标志,如 将自定义参数传递给控制平面组件 所述
  • 所有静态 Pod 都会获取用户指定的任何额外卷(主机路径)

请注意:

  1. --kubernetes-version 当前体系结构中的所有镜像 将从中 gcr.io/google_containers 中拉取; 如果指定了其他镜像仓库库或 CI 镜像仓库,则将使用此仓库; 如果一个特定的容器镜像应该被用于所有控制平面组件,那么这个特定镜像将被使用。请参阅 使用自定义镜像 了解更多详情
  2. 如果在 --dry-run 模式下执行 kubeadm,则将静态 Pod 文件写入临时文件夹
  3. 可以使用 kubeadm alpha phase controlplane all 命令单独调用生成主组件的静态 Pod 清单

API server

API server 的静态 Pod 清单受用户提供的以下参数的影响:

  • 需要指定要绑定到的 apiserver-advertise-address 和 apiserver-bind-port;如果没有提供,这些值分别默认为机器上默认网络接口的 IP 地址和端口 6443
  • service-cluster-ip-range 用于服务
  • 如果指定了外部 etcd 服务器,则要设定 etcd-servers 地址和相关的 TLS 设置(etcd-cafile、etcd-certfile、etcd-keyfile); 如果不提供外部 etcd 服务器,则会使用本地 etcd(通过主机网络)
  • 如果指定了云提供商,则要配置相应的 --cloud-provider,如果这样的文件存在,还要配置 --cloud-config 路径(这是实验性的、alpha 功能,将在未来的版本中删除)
  • 如果 kubeadm 被调用为 --feature-gates=HighAvailability,则标志 --endpoint-reconciler-type=lease 被设置,从而启用内部 API server VIP 的 endpoints 的自动协调
  • 如果 kubeadm 被调用为 --feature-gates=DynamicKubeletConfig,则 API 服务器上的相应功能将通过 --feature-gates=DynamicKubeletConfig=true 标志激活

其他无条件设置的 API server 标志是:

  • --insecure-port=0 避免与 api server 的不安全连接
  • --enable-bootstrap-token-auth=true 启用 BootstrapTokenAuthenticator 验证模块。有关更多详细信息,请参阅 TLS 引导
  • --allow-privileged 为 true (如 kube proxy 所要求的)
  • --requestheader-client-ca-file 为 front-proxy-ca.crt
  • --admission-control 为:
    • Initializers 启用 动态准入控制
    • NamespaceLifecycle 例如避免删除系统保留的命名空间
    • LimitRanger 和 ResourceQuota 强制限制命名空间
    • ServiceAccount 强制执行服务帐户自动化
    • PersistentVolumeLabel 将区域或区域标签附加到由云提供商定义的 PersistentVolumes (此准入控制器已被弃用,并将在未来的版本中被删除。没有明确选择使用 gce 或 aws 作为云提供商时,它在默认情况下跟 1.9 版本一样,并不是由 kubeadm 部署)
    • DefaultStorageClass 在 PersistentVolumeClaim 对象上强制执行默认存储类
    • DefaultTolerationSeconds
    • NodeRestriction 限制 kubelet 可以修改的内容(例如,只有该节点上的 pod)
  • --kubelet-preferred-address-types 为 InternalIP,ExternalIP,Hostname;,这使得 kubectl logs 和其他 api server-kubelet 通信能够在节点主机名不可解析的环境中工作。
  • 使用先前步骤中生成的证书的标志:
    • --client-ca-file 为 ca.crt
    • --tls-cert-file 为 apiserver.crt
    • --tls-private-key-file 为 apiserver.key
    • --kubelet-client-certificate 为 apiserver-kubelet-client.crt
    • --kubelet-client-key 为 apiserver-kubelet-client.key
    • --service-account-key-file 为 sa.pub
    • --requestheader-client-ca-file为front-proxy-ca.crt
    • --proxy-client-cert-file 为 front-proxy-client.crt
    • --proxy-client-key-file 为 front-proxy-client.key
  • 用于保护前端代理(API Aggregation)通信的其他标志:
    • --requestheader-username-headers=X-Remote-User
    • --requestheader-group-headers=X-Remote-Group
    • --requestheader-extra-headers-prefix=X-Remote-Extra-
    • --requestheader-allowed-names=front-proxy-client

Controller manager

API server 的静态 Pod 清单受用户提供的以下参数的影响:

  • 如果调用 kubeadm 时指定一个 --pod-network-cidr,某些 CNI 网络插件所需的子网管理器功能可以通过设置来启用:
    • --allocate-node-cidrs=true
    • --cluster-cidr 和 --node-cidr-mask-size 根据给定的 CIDR 标志
  • 如果指定了云提供商,则要配置相应的 --cloud-provider,如果这样的文件存在,还要配置 --cloud-config 路径(这是实验性的、alpha 功能,将在未来的版本中删除)

其他无条件设置的标志是:

  • --controllers 为 TLS 引导启用所有默认控制器加上 BootstrapSigner 和 TokenCleaner 控制器。有关更多详细信息,请参阅 TLS 引导
  • --use-service-account-credentials为 true
  • 使用先前步骤中生成的证书的标志:
    • --root-ca-file 为 ca.crt
    • --cluster-signing-cert-file 为 ca.crt,如果外部 CA 模式被禁用,则返回 ""
    • --cluster-signing-key-file 为 ca.key,如果外部 CA 模式被禁用,则返回 ""
    • --service-account-private-key-file 为 sa.key

Scheduler

Scheduler 的静态 Pod 清单不受用户提供的参数的影响。

为本地 etcd 生成静态 Pod 清单

如果用户指定了外部 etcd,则此步骤将被跳过,否则 kubeadm 将生成一个静态的 Pod 清单文件,用于创建在 Pod 中运行的本地 etcd 实例,其中包含以下属性:

  • 监听 localhost:2379 并使用 HostNetwork=true
  • 做一个 hostPath,从 dataDir 挂载到 主机文件系统
  • 任何由用户指定的额外标志

请注意:

  1. etcd 镜像将从中 gcr.io/google_containers 中拉取; 如果指定了其他镜像仓库库,则将使用此仓库; 如果一个特定的容器镜像应该被用于所有控制平面组件,那么这个特定镜像将被使用。请参阅 使用自定义镜像 了解更多详情
  2. 如果在 --dry-run 模式下执行 kubeadm,则将静态 Pod 文件写入临时文件夹
  3. 可以使用 kubeadm alpha phase etcd local 命令为本地 etcd 生成的静态 Pod 清单

(可选,1.9 版本中为 alpha)编写 init kubelet 配置

如果 kubeadm 被调用为 --feature-gates=DynamicKubeletConfig,它会将 kubelet init 配置写入 /var/lib/kubelet/config/init/kubelet 文件。

init 配置用于在此特定节点上启动 kubelet,为 kubelet 插入文件提供替代方案; 这种配置将被以下步骤中所述的 Kubelet 基本配置替代。请参阅 通过配置文件设置 Kubelet 参数 以获取更多信息。

请注意:

  1. 要使动态 kubelet 配置正常工作,应该在 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 中指定标志 --dynamic-config-dir=/var/lib/kubelet/config/dynamic
  2. 通过设置.kubeletConfiguration.baseConfig,Kubelet init 配置可以通过使用 kubeadm MasterConfiguration 文件进行修改。请参阅 在配置文件中使用 kubelet init 以获取更多信息。

等待控制平面启动

这是 kubeadm 集群的关键时刻。kubeadm 等待 localhost:6443/healthz 返回 ok,但是为了检测死锁情况,如果localhost:10255/healthz(kubelet liveness)或 localhost:10255/healthz/syncloop(kubelet readiness)分别在 40 秒和 60 秒后不返回 ok,kubeadm 就会快速失败。

kubeadm 依靠 kubelet 来拉取控制平面镜像,并以静态 Pod 的形式正确运行它们。控制平面启动后,kubeadm 完成以下段落中描述的任务。

(可选,1.9 版本中为 alpha)编写基本 kubelet 配置

如果 kubeadm 被调用为 --feature-gates=DynamicKubeletConfig:

  1. 将 kubelet 基本配置写入命名空间 kube-system 的 kubelet-base-config-v1.9 ConfigMap 中
  2. 创建 RBAC 规则来授予该 ConfigMap 对所有引导令牌和所有 kubelet 实例(即组 system:bootstrappers:kubeadm:default-node-token 和 system:nodes)的读访问权限
  3. 通过将 Node.spec.configSource 指向新创建的 ConfigMap 来为初始主节点启用动态 kubelet 配置功能

将 kubeadm MasterConfiguration 保存在 ConfigMap 中供以后参考

kubeadm 将 kubeadm init 通过标志或配置文件传递给 ConfigMap 的配置保存在 kube-system 命名空间下的 kubeadm-config ConfigMap 中。

这将确保将来(例如 kubeadm upgrade)执行的 kubeadm 行动将能够确定 实际/当前 的集群状态并基于该数据做出新的决定。

请注意:

  1. 在上传之前,敏感信息(例如令牌)会从配置中删除
  2. 主配置的上传可以通过 kubeadm alpha phase upload-config 命令单独调用
  3. 如果您使用 kubeadm v1.7.x 或更低版本初始化集群,则必须在使用 kubeadm upgrade 到 v1.8 之前手动创建 master 的配置 ConfigMap 。为了促进这项任务,kubeadm config upload (from-flags|from-file) 已经实施

标记 master

一旦控制平面可用,kubeadm 将执行以下操作:

  • 用 node-role.kubernetes.io/master="" 给 master 增加标签
  • 用 node-role.kubernetes.io/master:NoSchedule 给 master 增加污点

请注意:

  1. 标记 master 阶段可以通过 kubeadm alpha phase mark-master 命令单独调用

配置 TLS-引导 以加入节点

Kubeadm 使用 引导令牌进行身份验证 将新节点连接到现有集群; 欲了解更多详情,请参阅 设计方案

kubeadm init 确保为此过程正确配置所有内容,这包括以下步骤以及设置 API server 和控制器标志,如前面几个段落中所述。

请注意:

  1. 可以使用 kubeadm alpha phase bootstrap-token all 命令配置节点的 TLS 引导,执行以下段落中描述的所有配置步骤; 或者,每个步骤都可以单独调用

创建一个引导令牌

kubeadm init 创建第一个引导令牌,可以自动生成或由用户使用 --token 标志提供; 在引导令牌规范中,令牌应该保存为命名空间 kube-system 下的 bootstrap-token-<token-id> secret 中。

请注意:

  1. 通过 kubeadm init 创建的默认令牌将用于 TLS 在引导过程中验证临时用户;这些用户将成为 system:bootstrappers:kubeadm:default-node-token 组的成员
  2. 令牌的有效期有限,默认 24 小时(间隔可以使用 —token-ttl 标志变更)
  3. 额外的令牌可以使用 kubeadm token 命令创建,它还可以为令牌管理提供其他有用的功能

允许加入节点来调用 CSR API

Kubeadm 确保 system:bootstrappers:kubeadm:default-node-token 组中的用户能够访问证书签名 API。

这是通过在上面的组和默认的 RBAC 角色 system:node-bootstrapper 之间创建一个名为 kubeadm:kubelet-bootstrap 的 ClusterRoleBinding 来实现的。

为新的引导令牌设置自动批准

Kubeadm 确保引导令牌将获得 csrapprover 控制器自动批准的 CSR 请求。

这是通过 system:bootstrappers:kubeadm:default-node-token 组和默认的角色 system:certificates.k8s.io:certificatesigningrequests:nodeclient 之间创建一个名为 kubeadm:node-autoapprove-bootstrap 的 ClusterRoleBinding 来实现的。

角色 system:certificates.k8s.io:certificatesigningrequests:nodeclient 也应该创建,并授予访问 /apis/certificates.k8s.io/certificatesigningrequests/nodeclient 的 POST 权限。

通过自动批准设置节点证书轮换

Kubeadm 确保为节点启用证书轮换,并且节点的新证书请求将获得由 csrapprover 控制器自动批准的 CSR 请求。

这是通过 system:nodes 组和默认的角色 system:certificates.k8s.io:certificatesigningrequests:selfnodeclient 之间创建一个名为 kubeadm:node-autoapprove-certificate-rotation 的 ClusterRoleBinding 来实现的。

创建公共集群信息 ConfigMap

此阶段在 kube-public 命名空间中创建 cluster-info ConfigMap。

此外,还创建了一个角色和一个 RoleBinding,为未经身份验证的用户授予对 ConfigMap 的访问权(即 RBAC 组中的用户 system:unauthenticated)

请注意:

  1. 访问 cluster-info ConfigMap 是不 受限制的。如果您将您的主机暴露在互联网上,这可能是问题,也可能不是问题;最坏的情况是 DoS 攻击,攻击者使用 Kube-apiserver 可以处理的所有请求来为 cluster-info ConfigMap 提供服务。

安装插件

Kubeadm 通过 API server 安装内部 DNS 服务和 kube-proxy 插件组件。

请注意:

  1. 这个阶段可以通过 kubeadm alpha phase addon all 命令单独调用

代理

在命名空间 kube-system 下为 kube-proxy 创建一个 ServiceAccount;然后使用 DaemonSet 部署 kube-proxy:

  • master 的凭证(ca.crt 和 token)来自 ServiceAccount
  • master 的位置来自 ConfigMap
  • kube-proxy ServiceAccount 绑定到 system:node-proxier ClusterRole 中的权限

DNS

在命名空间 kube-system 下为 kube-dns 创建一个 ServiceAccount。

部署 kube-dns 的 Deployment 和 Service:

  • 这是相对上游来说没有修改的 kube-dns 部署
  • kube-dns ServiceAccount 绑定到 system:kube-dns ClusterRole 中的权限

请注意:

  1. 如果 kubeadm 被调用为 --feature-gates=CoreDNS,则会安装 CoreDNS 而不是 kube-dns

(可选,v1.9 中是 alpha)自托管

只有在 kubeadm init 被调用为 —features-gates=selfHosting 才执行此阶段

自托管阶段基本上用 DaemonSet 取代控制平面组件的静态 Pod; 这是通过执行 API server、scheduler 和 controller manager 静态 Pod 的以下过程来实现的:

  • 从磁盘加载静态 Pod 规格
  • 从静态的 Pod 清单文件中提取 PodSpec
  • 改变 PodSpec 与自托管兼容,更详细的内容:
    • 为带有 node-role.kubernetes.io/master="" 标签的节点增加节点选择器属性
    • 为污点 node-role.kubernetes.io/master:NoSchedule 增加一个容忍
    • 设置 spec.DNSPolicy 为 ClusterFirstWithHostNet
  • 为有问题的自托管组件构建一个新的 DaemonSet 对象。使用上面提到的 PodSpec
  • 在 kube-system 命名空间中创建 DaemonSet 资源。等到 Pod 运行。
  • 删除静态的 Pod 清单文件。kubelet 将停止正在运行的原始静态 Pod 托管组件

请注意:

  1. 自托管尚未恢复到节点重新启动的能力; 这可以通过外部检查点或控制平面 Pod 的 kubelet 检查点来修正。有关更多详细信息,请参阅 自托管
  2. 如果被调用为 —features-gates=StoreCertsInSecrets,以下附加步骤将被执行
    • 在 kube-system 命名空间下使用各自的证书和秘钥创建 ca、apiserver、apiserver-kubelet-client、sa、front-proxy-ca、front-proxy-client TLS secrets 。重要!将 CA 密钥存储在 Secret 中可能会产生安全隐患
    • 使用各自的 kubeconfig 文件在命名空间 kube-system 中创建 schedler.conf 和 controller-manager.conf secret
    • 通过将主机路径卷替换为上述 secret 中的投影卷,对所有 POD 规范进行变更
  3. 这个阶段可以通过 kubeadm alpha phase selfhosting convert-from-staticpods 命令单独调用

kubeadm join 阶段的内部设计

与 kubeadm init 类似,kubeadm join 内部工作流也是由一系列要执行的原子工作任务组成。

这分为发现(有 Node 信任 Kubernetes Master)和 TLS 引导(有 Kubernetes Master 信任 Node)。

请参阅 使用引导令牌进行身份验证 或相应的 设计方案

预检检查

kubeadm 在开始连接之前执行一组预检检查,目的是验证先决条件并避免常见的集群启动问题。

请注意:

  1. kubeadm join 预检检查基本上是一个 kubeadm init 预检检查的子集
  2. 从 1.9 开始,kubeadm 为 CRI 泛型功能提供了更好的支持; 在这种情况下,docker 特定的控件将被跳过或替换为 crictl 类似控件
  3. 从 1.9 开始,kubeadm 支持加入运行在 Windows 上的节点; 在这种情况下,会跳过 linux 特定的控制
  4. 在任何情况下,用户都可以使用该 --ignore-preflight-errors 选项跳过特定的预检检查(或最终所有预检检查)

发现集群信息

有两个主要的发现方案。首先是使用共享令牌以及 API server 的 IP 地址。第二个是提供一个文件(标准 kubeconfig 文件的一个子集)。

共享令牌发现

如果 kubeadm join 被调用为 --discovery-token,则使用令牌发现; 在这种情况下,节点基本上从命名空间 kube-public 下 cluster-info ConfigMap 中检索集群 CA 证书 。

为了防止 “中间人” 攻击,采取了几个步骤:

  • 首先,通过不安全的连接检索 CA 证书(这是可能的,因为 kubeadm init 对 system:unauthenticated 授予了访问 cluster-info 用户的权限)
  • 然后 CA 证书通过以下验证步骤:
    • 基本验证:针对 JWT 签名使用令牌 ID
    • 发布密钥验证:使用提供的 --discovery-token-ca-cert-hash。此值可在 kubeadm init 的输出中获取,也可以使用标准工具计算(散列是在 SPKI(Subject Public Key Info)对象的字节上计算的,如 RFC 7469 中所示)。--discovery-token-ca-cert-hash 标志可以重复多次,以允许多个公钥。 -作为附加验证,CA 证书通过安全连接进行检索,然后与最初检索的 CA 进行比较

请注意:

  1. 通过 --discovery-token-unsafe-skip-ca-verification 标志可以跳过发布密钥验证; 这削弱了 kubeadm 安全模型,因为其他人可能潜在模仿 Kubernetes Master。

文件/https 发现

如果 kubeadm join 被调用为 --discovery-file,则使用文件发现; 此文件可以是本地文件或通过 HTTPS URL 下载; 在 HTTPS 的情况下,主机安装的 CA 用于验证连接。

通过文件发现,集群 CA 证书被提供到文件本身; 事实上,发现的文件是一个 kubeconfig 文件,其中只设置了 server 和 certificate-authority-data 属性,如 kubeadm join 参考文档中所述; 当与集群建立连接时,kubeadm 尝试访问 cluster-info ConfigMap,如果可用,则使用它。

TLS 引导

一旦知道了集群信息,就会编写文件 bootstrap-kubelet.conf,从而允许 kubelet 执行 TLS 引导(相反,直到 v1.7 TLS 引导被 kubeadm 管理)。

TLS 引导机制使用共享令牌临时向 Kubernetes Master 进行身份验证,以提交本地创建的密钥对的证书签名请求(CSR)。

然后自动批准该请求,并且该操作完成保存 ca.crt 文件和用于加入集群的 kubelet.conf 文件,而 bootstrap-kubelet.conf 被删除。

请注意:

  • 临时验证是根据 kubeadm init 过程中保存的令牌进行验证的(或者使用 kubeadm token 创建的附加令牌)
  • 对 kubeadm init 过程中被授予访问 CSR api 的 system:bootstrappers:kubeadm:default-node-token 组的用户成员的临时身份验证解析
  • 自动 CSR 审批由 csrapprover 控制器管理,与 kubeadm init 过程的配置相一致

(可选,1.9 版本中为 alpha)编写init kubelet配置

如果 kubeadm 被调用为 --feature-gates=DynamicKubeletConfig:

  1. 使用引导令牌凭据从 kube-system 命名空间中的 kubelet-base-config-v1.9 ConfigMap 中读取 kubelet 基本配置,并将其写入磁盘,作为 kubelet init 配置文件 /var/lib/kubelet/config/init/kubelet
  2. 当 kubelet 以节点自己的凭据(/etc/kubernetes/kubelet.conf)开始时,更新当前节点配置,指定 node/kubelet 配置的源是上面的 ConfigMap。

请注意:

  1. 要使动态 kubelet 配置正常工作,应在 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 中指定标志 --dynamic-config-dir=/var/lib/kubelet/config/dynamic

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

Rancher 与 Ubuntu Kubernetes 集成

本文说明如何在标准的 Kubernetes 上部署 Rancher 2.0alpha。

这些步骤目前处于 alpha/测试阶段,很可能会发生变化,截止到本文发布,Rancher 2.0正式版已经发布了。

有关此集成的原始文档可以在 https://github.com/CalvinHartwell/canonical-kubernetes-rancher/ 上找到。

Before you begin

要使用本指南,您必须拥有一个使用标准的 juju 部署的处于工作状态的 kubernetes 集群。

有关使用 juju 部署 Kubernetes 的完整说明,请访问 https://kubernetes.io/docs/getting-started-guides/ubuntu/installation/

部署 Rancher

想要部署 Rancher,我们只需要在 Kubernetes 上运行 Rancher 容器工作负载。Rancher 通过 dockerhub(https://hub.docker.com/r/rancher/server/tags/)提供他们的容器,并且可以从互联网免费下载。

如果您正在运行自己的仓库或进行离线部署,则在继续之前,应该下载容器镜像并其推入私有仓库。

使用 nodeport 部署 Rancher

首先创建一个 yaml 文件,该文件定义了如何在 kubernetes 上部署 Rancher。将该文件保存为 cdk-rancher-nodeport.yaml:

 ---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default
roleRef:
   kind: ClusterRole
   name: cluster-admin
   apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-admin
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: rancher
  name: rancher
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rancher
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: rancher
        ima: pod
    spec:
      containers:
      - image: rancher/server:preview
        imagePullPolicy: Always
        name: rancher
        ports:
        - containerPort: 80
        - containerPort: 443
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 30
        resources: {}
      restartPolicy: Always
      serviceAccountName: ""
status: {}
---
apiVersion: v1
kind: Service
metadata:
  name: rancher
  labels:
    app: rancher
spec:
  ports:
    - port: 443
      protocol: TCP
      targetPort: 443
  selector:
    app: rancher
---
apiVersion: v1
kind: Service
metadata: 
  name: rancher-nodeport
spec: 
  type: NodePort
  selector:
     app: rancher
  ports: 
  - name: rancher-api
    protocol: TCP
    nodePort: 30443
    port: 443
    targetPort: 443

一旦 kubectl 运行并处于工作状态,运行以下命令来部署 Rancher:

  kubectl apply -f cdk-rancher-nodeport.yaml

现在我们需要打开这个 nodeport,以便我们可以访问它。为此,我们可以使用 juju。我们需要为集群中的每个工作节点运行 open-port 命令。在 cdk-rancher-nodeport.yaml 文件中,nodeport 已设置为 30443。下面显示了如何在每个工作节点上打开端口:

   # 对集群中的每个 Kubernetes 工作节点重复此操作。
   juju run --unit kubernetes-worker/0 "open-port 30443"
   juju run --unit kubernetes-worker/1 "open-port 30443"
   juju run --unit kubernetes-worker/2 "open-port 30443"

现在可以通过工作节点 IP 或 DNS 条目在此端口上访问 Rancher(如果已创建它们)。通常建议您为集群中的每个工作节点创建一个 DNS 条目。例如,如果您有三个工作节点并且您拥有域 example.com,则可以创建三条 A 记录,每个集群中的每个工作节点都有一条记录。

由于创建 DNS 条目超出了本文的范围,因此我们将使用可免费获得的 xip.io 服务,该服务可返回 A 记录作为域名一部分的 IP 地址。例如,如果您有域 rancher.35.178.130.245.xip.io,则 xip.io 服务将自动将 IP 地址 35.178.130.245 作为 A 记录返回,这对于测试目的很有用。对于您的部署,IP 地址 35.178.130.245 应该替换为您的一个工作节点 IP 地址,您可以使用 Juju 或 AWS 找到该 IP 地址:

 calvinh@ubuntu-ws:~/Source/cdk-rancher$ juju status

# ... 输出省略. 

Unit                      Workload  Agent  Machine  Public address  Ports                     Message
easyrsa/0*                active    idle   0        35.178.118.232                            Certificate Authority connected.
etcd/0*                   active    idle   1        35.178.49.31    2379/tcp                  Healthy with 3 known peers
etcd/1                    active    idle   2        35.177.99.171   2379/tcp                  Healthy with 3 known peers
etcd/2                    active    idle   3        35.178.125.161  2379/tcp                  Healthy with 3 known peers
kubeapi-load-balancer/0*  active    idle   4        35.178.37.87    443/tcp                   Loadbalancer ready.
kubernetes-master/0*      active    idle   5        35.177.239.237  6443/tcp                  Kubernetes master running.
  flannel/0*              active    idle            35.177.239.237                            Flannel subnet 10.1.27.1/24
kubernetes-worker/0*      active    idle   6        35.178.130.245  80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/2               active    idle            35.178.130.245                            Flannel subnet 10.1.82.1/24
kubernetes-worker/1       active    idle   7        35.178.121.29   80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/3               active    idle            35.178.121.29                             Flannel subnet 10.1.66.1/24
kubernetes-worker/2       active    idle   8        35.177.144.76   80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/1               active    idle            35.177.144.76                        

# 注意上面例子中 kubernetes 工作节点的 IP 地址。您应该选择一个公网地址。

尝试使用 nodeport 和域名或 IP 地址在浏览器中打开 Rancher:

  # 将 IP 地址替换为您的 Kubernetes 工作节点之一,从 juju 状态命令中找到此地址。
  wget https://35.178.130.245.xip.io:30443 --no-check-certificate

  # 这应该也可以工作
  wget https://35.178.130.245:30443 --no-check-certificate 

如果您需要对 kubernetes 配置文件进行任何更改,请编辑 yaml 文件,然后再次使用 apply:

  kubectl apply -f cdk-rancher-nodeport.yaml

使用 ingress 规则部署 Rancher

也可以使用 ingress 规则来部署 Rancher。这还有另外一个好处,就是不需要在 Kubernetes 集群上打开额外的端口。首先创建一个 yaml 文件来描述名为 cdk-rancher-ingress.yaml 的部署,它应包含以下内容:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default
roleRef:
   kind: ClusterRole
   name: cluster-admin
   apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-admin
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: rancher
  name: rancher
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rancher
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: rancher
    spec:
      containers:
      - image: rancher/server:preview
        imagePullPolicy: Always
        name: rancher
        ports:
        - containerPort: 443
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 30
        resources: {}
      restartPolicy: Always
      serviceAccountName: ""
status: {}
---
apiVersion: v1
kind: Service
metadata: 
  name: rancher
  labels:
    app: rancher
spec: 
  ports: 
    - port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app: rancher
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 name: rancher
 annotations:
   kubernetes.io/tls-acme: "true"
   ingress.kubernetes.io/secure-backends: "true"
spec:
 tls:
   - hosts:
     - rancher.34.244.118.135.xip.io
 rules:
   - host: rancher.34.244.118.135.xip.io
     http:
       paths:
         - path: /
           backend: 
             serviceName: rancher
             servicePort: 443

通常建议您为集群中的每个工作节点创建一个 DNS 条目。例如,如果您有三个工作节点并且您拥有域 example.com,则可以创建三条 A 记录,每个集群中的每个工作人员都有一条记录。

由于创建 DNS 条目超出了本教程的范围,因此我们将使用可免费获得的 xip.io 服务,该服务可以返回 A 记录作为域名一部分的 IP 地址。例如,如果您有域 rancher.35.178.130.245.xip.io,则 xip.io 服务将自动将 IP 地址 35.178.130.245 作为 A 记录返回,这对于测试目的很有用。

对于您的部署,IP 地址 35.178.130.245 应该替换为您的一个工作节点 IP 地址,您可以使用 Juju 或 AWS 找到该 IP 地址:

 calvinh@ubuntu-ws:~/Source/cdk-rancher$ juju status

# ... 输出省略。

Unit                      Workload  Agent  Machine  Public address  Ports                     Message
easyrsa/0*                active    idle   0        35.178.118.232                            Certificate Authority connected.
etcd/0*                   active    idle   1        35.178.49.31    2379/tcp                  Healthy with 3 known peers
etcd/1                    active    idle   2        35.177.99.171   2379/tcp                  Healthy with 3 known peers
etcd/2                    active    idle   3        35.178.125.161  2379/tcp                  Healthy with 3 known peers
kubeapi-load-balancer/0*  active    idle   4        35.178.37.87    443/tcp                   Loadbalancer ready.
kubernetes-master/0*      active    idle   5        35.177.239.237  6443/tcp                  Kubernetes master running.
  flannel/0*              active    idle            35.177.239.237                            Flannel subnet 10.1.27.1/24
kubernetes-worker/0*      active    idle   6        35.178.130.245  80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/2               active    idle            35.178.130.245                            Flannel subnet 10.1.82.1/24
kubernetes-worker/1       active    idle   7        35.178.121.29   80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/3               active    idle            35.178.121.29                             Flannel subnet 10.1.66.1/24
kubernetes-worker/2       active    idle   8        35.177.144.76   80/tcp,443/tcp,30443/tcp  Kubernetes worker running.
  flannel/1               active    idle            35.177.144.76

# 注意上面例子中 kubernetes 工作节点的 IP 地址。您应该选择一个公网地址。

查看上述 juju 状态的输出,公共地址(35.178.130.245)可用于创建 xip.io DNS 条目(rancher.35.178.130.245.xip.io),该条目应放置在 cdk-rancher- ingress.yaml 文件。您也可以创建自己的 DNS 条目,只要它解析为每个工作节点或其中一个工作节点即可正常工作:

  # 文件中,xip.io 域应该出现在两个地方,都需要修改
  cat cdk-rancher-ingress.yaml | grep xip.io
  - host: rancher.35.178.130.245.xip.io

一旦编辑了 ingress 规则以映射您的 DNS 条目,请运行 kubectl apply -f cdk-rancher-ingress.yaml 来部署 Kubernetes:

 kubectl apply -f cdk-rancher-ingress.yaml

现在可以通过工作节点 IP 或 DNS 条目在常规 443 上访问 Rancher(如果已创建它们)。尝试在浏览器中打开它:

  # 将 IP 地址替换为您的 Kubernetes 工作节点之一,从 juju 状态命令中找到此地址。
  wget https://35.178.130.245.xip.io:443 --no-check-certificate

如果您需要对 kubernetes 配置文件进行任何更改,请编辑 yaml 文件,然后再次使用 apply:

  kubectl apply -f cdk-rancher-ingress.yaml

移除 Rancher

您可以使用 kubectl 从集群中删除 Rancher。在 Kubernetes 中删除结构与创建它们一样简单:

  # 使用 nodeport 示例(如果使用 ingress 示例,请修改文件名)
  kubectl delete -f cdk-rancher-nodeport.yaml

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

kubernetes 调试 DNS 解析

此页面提供有关诊断 DNS 问题的提示。

Before you begin

  • You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using Minikube, or you can use one of these Kubernetes playgrounds:
  • Katacoda
  • Play with Kubernetes

To check the version, enter kubectl version.

  • Kubernetes 版本 1.6 及以上。
  • 该集群必须配置为使用 kube-dns 插件。

创建一个简单的 Pod 来用作测试环境

使用以下内容创建一个名为 busybox.yaml 的文件:

busybox.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

然后使用此文件创建一个 pod 并验证其状态:

$ kubectl create -f busybox.yaml
pod "busybox" created

$ kubectl get pods busybox
NAME      READY     STATUS    RESTARTS   AGE
busybox   1/1       Running   0          <some-time>

一旦该 pod 运行,您就可以在环境中执行 nslookup。如果您看到如下所示的内容,则 DNS 工作正常。

$ kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10

Name:      kubernetes.default
Address 1: 10.0.0.1

如果 nslookup 命令失败,请检查以下内容:

首先检查本地 DNS 配置

看一看 resolv.conf 文件。(有关更多信息,请参阅 从节点继承 DNS 和 下面的 已知问题

$ kubectl exec busybox cat /etc/resolv.conf

验证搜索路径和名称服务器是否设置如下(请注意,搜索路径可能因不同的云提供商而异):

search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal
nameserver 10.0.0.10
options ndots:5

以下错误表明 kube-dns 附加组件或相关服务存在问题:

$ kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10

nslookup: can't resolve 'kubernetes.default'

或者

$ kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

nslookup: can't resolve 'kubernetes.default'

检查 DNS pod 是否正在运行中

使用 kubectl get pods 命令验证 DNS pod 是否正在运行中。

$ kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
NAME                    READY     STATUS    RESTARTS   AGE
...
kube-dns-v19-ezo1y      3/3       Running   0           1h
...

如果您看到没有 pod 正在运行中,或者 pod 已失败/已完成,那么在当前环境中,默认情况下可能不会部署 DNS 插件,您将不得不手动部署它。

检查 DNS pod 中的错误

使用 kubectl logs 命令查看 DNS 守护程序的日志。

$ kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c kubedns
$ kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c dnsmasq
$ kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c sidecar

看看有没有可疑的日志。字母 ‘W‘、’E‘、’F’ 表示警告、错误和失败。请搜索具有这些日志级别的条目,并使用 kubernetes 问题 来报告意外错误。

DNS服务起来了吗?

通过使用 kubectl get service 命令验证 DNS 服务已启动。

$ kubectl get svc --namespace=kube-system
NAME          CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
...
kube-dns      10.0.0.10      <none>        53/UDP,53/TCP        1h
...

如果您已经创建了该服务,或者应该在默认情况下创建它,但它没有出现,请参阅 调试服务 以获取更多信息。

DNS endpoints 是否暴露?

您可以使用 kubectl get endpoints 命令验证是否暴露了了 DNS endpoints。

$ kubectl get ep kube-dns --namespace=kube-system
NAME       ENDPOINTS                       AGE
kube-dns   10.180.3.17:53,10.180.3.17:53    1h

如果您没有看到 endpoints,请参阅 调试服务 文档中的 endpoints 部分 。

有关其他 Kubernetes DNS 示例,请参阅 Kubernetes GitHub 仓库中的 cluster-dns 示例

已知问题

Kubernetes 安装不会将节点的 resolv.conf 文件配置为默认使用集群 DNS,因为该过程本身就是发行版的。最终可能会这么实现。

Linux 的 libc 不可能摆脱(见 2005 年的这个 bug)只有 3 个 DNS nameserver 记录和 6 个 DNS search 记录的限制。Kubernetes 需要消耗 1 个 nameserver 记录和 3 条 search 记录。这意味着如果本地安装已经使用了 3 个 nameserver 或使用了多于 3 条 search,那么其中一些设置将会丢失。作为部分解决方法,节点可以运行 dnsmasq,它将提供更多 nameserver 条目,但没有更多的 search 条目。您也可以使用 kubelet --resolv-conf 标志。

如果您使用 Alpine 3.3 或更低版本作为您的基本镜像,由于 Alpine 的某些已知问题,DNS 可能无法正常工作。点击 这里 查看更多信息。

Kubernetes Federation(多区域支持)

版本 1.3 引入了用于多站点 Kubernetes 安装的集群 Federation 支持。这需要对 Kubernetes 集群 DNS 服务器处理 DNS 查询的方式进行一些小的(向后兼容)更改,以便于查找 Federation 服务(跨多个 Kubernetes 集群)。有关 Cluster Federation 和多站点支持的更多详细信息,请参见 Cluster Federation 管理员指南

参考

译者:chentao1596 / 原文链接

K8S中文社区微信公众号

kubernetes 排查应用故障

本指南旨在帮助用户调试那些部署到 Kubernetes 中但是行为不正确的应用。这 不是 针对想要调试集群的人员的指南。如果想要调试集群,您应该看看 这个指南。

诊断问题

故障排查的第一步是分诊。问题是什么?是您的 Pod?您的副本控制器(RC:Replication Controller)?还是您的服务?

  • 调试 Pod
  • 调试副本控制器
  • 调试服务

调试 Pod

调试 Pod 的第一步是查看它。使用以下命令检查 Pod 以及最近事件的状态:

$ kubectl describe pods ${POD_NAME}

查看 pod 中容器的状态。它们都是Running?最近有没有重新启动?

根据 pod 的状态继续调试。

pod 一直是 pending 状态

如果 pod 卡在 Pending 状态,意味着它不能被调度到节点上。通常这是因为没有足够的资源导致调度被阻止了。查看 kubectl describe ... 命令的输出。应该有来自调度器说明为什么它无法调度您的 pod 的消息。原因包括:

  • 您没有足够的资源:您可能已经耗尽了集群中 CPU 或内存的供应,在这种情况下,您需要删除 Pod、调整资源请求或向集群添加新节点。
  • 您正在使用 hostPort:当您将 Pod 绑定到一个 hostPort,可调度的 pod 的数量是有限的。在大多数情况下,hostPort 是不需要的,可以尝试使用 Service 对象来暴露您的 Pod。如果您确实需要 hostPort,那么您只能调度与 Kubernetes 集群中节点数量相同的 Pod。

pod 一直是 waiting 状态

如果一个 Pod 停留在 Waiting 状态,则它已被调度到一个工作节点,但不能在该机器上运行。同样,来自 kubectl describe ... 命令的信息应该是可以分析其原因的。Waitingpod 最常见的原因是无法拉取镜像。有三件事要检查:

  • 确保您有正确的镜像名称。
  • 您是否将镜像推送到仓库?
  • 在您的机器运行 docker pull <image>,查看镜像是否能够拉取。

pod 崩溃或者其它不健康状态

首先,查看当前容器的日志:

$ kubectl logs ${POD_NAME} ${CONTAINER_NAME}

如果您的容器先前已崩溃,则可以通过以下方式访问先前容器的崩溃日志:

$ kubectl logs --previous ${POD_NAME} ${CONTAINER_NAME}

或者,您可以在该容器中运行命令 exec:

$ kubectl exec ${POD_NAME} -c ${CONTAINER_NAME} -- ${CMD} ${ARG1} ${ARG2} ... ${ARGN}

请注意,-c ${CONTAINER_NAME} 是可选的,对于只包含单个容器的 pod 可以省略。

例如,要查看正在运行的 Cassandra pod 的日志,您可以运行

$ kubectl exec cassandra -- cat /var/log/cassandra/system.log

如果这些方法都不起作用,您可以找到运行 pod 的主机并通过 SSH 连接到该主机,但是,在 Kubernetes API 中,通常是没有必要使用这些工具的。因此,如果您发现自己需要 SSH 进入一台机器,请在 GitHub 上提交描述您使用案例的功能请求,以及为什么这些工具不足。

pod 正在运行,但没有按照我所说的去做

如果您的 pod 没有按照您的预期运行,则可能是您的 pod 描述中存在错误(例如,本地计算机上的 mypod.yaml 文件),并且在创建 pod 时错误被默默地忽略了。通常情况下,包括 pod 描述的一部分被错误的嵌套、或者某个键名称键入错误,因此该键被忽略。例如,如果将 command 错误地拼写为 commnd,那么会创建 pod,但不会使用您打算使用的命令行。

首先要做的是删除您的 pod,然后尝试使用 --validate 选项重新创建它。例如,运行 kubectl create --validate -f mypod.yaml。如果将 command 错误地拼写为 commnd,那么会出现如下错误:

I0805 10:43:25.129850   46757 schema.go:126] unknown field: commnd
I0805 10:43:25.129973   46757 schema.go:129] this may be a false alarm, see https://github.com/kubernetes/kubernetes/issues/6842
pods/mypod

接下来要检查 apiserver 上的 pod 是否与您要创建的 pod 相匹配(例如,在本地机器上的 yaml 文件)。例如,运行 kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml并手动比较原始 pod 描述和从 apiserver 获取的描述 mypod-on-apiserver.yaml。“apiserver” 版本通常会有一些不在原始版本上出现的行,这是预料之中的。但是,如果原件上的某些行在 apiserver 版本上没有,则可能表明您的 pod 描述存在问题。

调试副本控制器

副本控制器相当直接。它们要么能够创建 Pod 要么不能。如果他们无法创建 pod,请参阅 上面的说明 来调试您的 pod。

您也可以使用 kubectl describe rc ${CONTROLLER_NAME} 内省与副本控制器相关的事件。

调试服务

服务提供跨一组 Pod 的负载平衡。有几个常见问题可能会导致服务无法正常工作。以下说明应该有助于调试服务问题。

首先,确认服务有 endpoints。对于每个服务对象,apiserver 都提供 endpoints 资源。

您可以查看这个资源:

$ kubectl get endpoints ${SERVICE_NAME}

确保 endpoints 符合您希望成为服务成员的容器数量。例如,如果您的服务是针对具有 3 个复本的 nginx 容器,则您希望在服务的 endpoints 中看到三个不同的 IP 地址。

服务缺少 endpoints

如果缺少 endpoints,请尝试使用 Service 中使用的标签列出 pod。想象一下,您有一个服务,标签是:

...
spec:
  - selector:
     name: nginx
     type: frontend

您可以使用:

$ kubectl get pods --selector=name=nginx,type=frontend

列出与此选择器匹配的 pod。请验证列表是否与您希望提供服务的 pod 相匹配。

如果 pod 列表符合期望值,但您的 endpoints 仍为空,则可能是因为没有暴露正确的端口。如果您的服务有一个指定的 containerPort,但所选的 Pod 没有列出该端口,则不会将其添加到 endpoint 列表中。

验证 pod 的 containerPort与服务的containerPort 匹配

网络流量未被转发

如果您可以连接到服务,但连接会马上断开,并且 endpoints 列表中有 endpoints,则有可能是代理无法联系到您的 pod。

有三件事要检查:

  • 您的 pod 工作正常吗?查找重新启动次数,并 调试 pod。
  • 您可以直接连接到您的 pod 吗?获取 Pod 的 IP 地址,并尝试直接连接到该 IP。
  • 您的应用是否在您配置的端口上运行?Kubernetes 不会执行端口重映射,所以如果您的应用在 8080 上运行,则该 containerPort 字段需要为8080。

更多信息

如果以上都不能解决您的问题,请按照 调试服务文档 中的说明操作,以确保您的 Service 正在运行、拥有 Endpoints 以及您的 Pods 确实正在服务中;您有正在工作中的 DNS、安装了 iptables 规则,并且 kube-proxy 似乎没有什么不妥之处。

译者:chentao1596 / 原文链接

K8S中文社区微信公众号