查看原文
其他

Kubernetes 基于 kubeadm 的安全配置和高可用

高朋 K8S中文社区 2019-12-18

点击上方“K8S中文社区”,选择“置顶公众号”

关键时刻,第一时间送达!


作者:高朋

就职于科赛网,主要负责整个数据分析平台后端的 k8s 架构,和一些数据分析和机器学习的基础架构的学习研究。


最新的 kubeadm 的设计文档在这里,如果把里面设计大概看一遍就能够理解里面的流程了,可以说设计的还是很缜密的,并且大大简化了 k8s 的运维工作。


kubeadm 安全设施

主要解释 kubeadm init 和 kubeadm join 的过程和实现


kubeadm init


1、首先进行 preflight-checks ,检查系统是否满足初始化的状态。 

2、创建自签名的 CA,并且生成和签发各个 component 的私钥和证书 (/etc/kubernetes/pki),如果文件已存在就不会再生成了,比如要给 apiserver 添加域名可以重新签发一个证书,然后重启就好了。 

3、写入各个服务的配置文件,以及一个 admin.conf (/etc/kubernetes/) 

4、配置 kubelet 的动态配置加载 (disable by default) 

5、配置静态 pod (/etc/kubernetes/manifests) 

6、给 master 添加 taint 和 label,让其他 pod 默认不会运行在 master 上 

7、生成用于让其他 kubelet 加入的 token 

8、配置用 token 加入的可以自动确认 CSR(也就是用 CA 自动签 kubelet 的证书) 

9、设置 kube-dns 

10、检查 self-hosting,如果设置了,就把 static pod 转成 daemonset 是否使用外部 CA 的条件是,目录下有 CA 证书,但是没有 CA 私钥。


  1. if res, _ := certsphase.UsingExternalCA(i.cfg); !res {

  2.        // PHASE 1: Generate certificates

  3.        if err := certsphase.CreatePKIAssets(i.cfg); err != nil {

  4.                return err

  5.        }

  6.        // PHASE 2: Generate kubeconfig files for the admin and the kubelet

  7.        if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil {

  8.                return err

  9.        }

  10. } else {

  11.        fmt.Println("[externalca] The file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated.")

  12. }


具体的生成 PKI 相关的配置的过程


  1. // CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.

  2. // If the PKI assets already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned.

  3. func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error {

  4.        certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{

  5.                CreateCACertAndKeyFiles,

  6.                CreateAPIServerCertAndKeyFiles,

  7.                CreateAPIServerKubeletClientCertAndKeyFiles,

  8.                CreateEtcdCACertAndKeyFiles,

  9.                CreateEtcdServerCertAndKeyFiles,

  10.                CreateEtcdPeerCertAndKeyFiles,

  11.                CreateEtcdHealthcheckClientCertAndKeyFiles,

  12.                CreateAPIServerEtcdClientCertAndKeyFiles,

  13.                CreateServiceAccountKeyAndPublicKeyFiles,

  14.                CreateFrontProxyCACertAndKeyFiles,

  15.                CreateFrontProxyClientCertAndKeyFiles,

  16.        }

  17.        for _, action := range certActions {

  18.                err := action(cfg)

  19.                if err != nil {

  20.                        return err

  21.                }

  22.        }

  23.        fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir)

  24.        return nil

  25. }


列表中的函数都是用来生成所有证书和私钥的,主要依靠 k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil 来完成。私钥很容易生成,没什么需要特别配置的,主要看 CA 的证书里面配了啥,因为这个跟 k8s 的鉴权有关系。


  1. // NewSelfSignedCACert creates a CA certificate

  2. func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, error) {

  3.        now := time.Now()

  4.        tmpl := x509.Certificate{

  5.                SerialNumber: new(big.Int).SetInt64(0),

  6.                Subject: pkix.Name{

  7.                        CommonName:   cfg.CommonName,

  8.                        Organization: cfg.Organization,

  9.                },

  10.                NotBefore:             now.UTC(),

  11.                NotAfter:              now.Add(duration365d * 10).UTC(),

  12.                KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,

  13.                BasicConstraintsValid: true,

  14.                IsCA: true,

  15.        }

  16.        certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)

  17.        if err != nil {

  18.                return nil, err

  19.        }

  20.        return x509.ParseCertificate(certDERBytes)

  21. }


用 openssl x509 -in /etc/kubernetes/pki/ca.crt -text -noout 可以查看 ca 证书里面的内容,和我们看到的配置是一致的。 



然后我们再看一下 apiserver 的私钥和证书是怎么签的,首先把生成的 CA 证书和私钥加载进来,然后生成私钥并且签出自己的证书。


  1. func CreateAPIServerCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {

  2.        caCert, caKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)

  3.        if err != nil {

  4.                return err

  5.        }

  6.        apiCert, apiKey, err := NewAPIServerCertAndKey(cfg, caCert, caKey)

  7.        if err != nil {

  8.                return err

  9.        }

  10.        return writeCertificateFilesIfNotExist(

  11.                cfg.CertificatesDir,

  12.                kubeadmconstants.APIServerCertAndKeyBaseName,

  13.                caCert,

  14.                apiCert,

  15.                apiKey,

  16.        )

  17. }


这里比较重要,因为 SAN 这个是用来匹配域名的,如果这里没写好,HTTPS 是拒绝访问的。


  1. // NewAPIServerCertAndKey generate certificate for apiserver, signed by the given CA.

  2. func NewAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {

  3.        altNames, err := pkiutil.GetAPIServerAltNames(cfg)

  4.        if err != nil {

  5.                return nil, nil, fmt.Errorf("failure while composing altnames for API server: %v", err)

  6.        }

  7.        config := certutil.Config{

  8.                CommonName: kubeadmconstants.APIServerCertCommonName,

  9.                AltNames:   *altNames,

  10.                Usages:     []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},

  11.        }

  12.        apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)

  13.        if err != nil {

  14.                return nil, nil, fmt.Errorf("failure while creating API server key and certificate: %v", err)

  15.        }

  16.        return apiCert, apiKey, nil

  17. }



apiserver 的证书也是对的,马赛克的部分是我自己配的一些 IP 和 域名,和 kubeadm 配的一些 kube-dns 用的 overlay 网络上的 master 域名,其他 master 上的 components 也是类似的,在配高可用的时候要把每个 master 节点上的域名和 IP 都配上。kubelet 的证书也要配对,就在 join 里面介绍了,这个会用来鉴权 node 的身份。


其他部分的代码和设计文档的描述是一致的,所以没什么好看的,感觉一个好的设计文档是非常重要的,代码只是设计的实现,如果设计本身就可读性很强,阅读代码只是辅助理解一些细节和找 BUG 用的而已。


kubeadm join


1、首先用 token 鉴权的方式获取 apiserver 的 CA 证书,并且通过 SHA256 验证。 

2、加载动态配置,如果 master 上有的话。 

3、TLS 初始化,首先用 token 鉴权的方式把自己的 CSR 发给 apiserver,然后签发自己的证书,这个证书要用来检查 node 的身份的。 

4、配置 kubelet 和 server 开始建立连接。 

新版本的 kubelet 有些变动,支持把一些 featuregate 配置写到文件里面,比如这个阻止 fork bomb 的配置,新版本只能用配置文件写了,不能通过参数配置。


kubeadm 的详细过程如下:


首先会把 flags 传入NodeConfiguration 中,开始 AddJoinConfigFlags(cmd.PersistentFlags(),如果没有 nodeNamecfg 默认用 host 的 name 并且小写化通过 GetHostname获得,和在宿主机上执行 hostname 是一致的。在初始化之前先 尝试启动 TryStartKubelet 过程,然后把 token 和 server 写入(证书 data 是怎么生成的?),先配置kubelet-bootstrap-config

整体流程如下,token 验证是基于 JWT 的,所以 token 的格式是 "^([a-z0-9]{6})\.([a-z0-9]{16})$",分两部分,tokenID.tokenSecret,


  1. discovery.For

  2. ->GetValidatedClusterInfoObject 这个是获取 CA 证书的过程

  3.     ->token.RetrieveValidatedClusterInfo 这一步是 JWT 验证

  4.         ->tokenutil.ParseToken   得到 tokenID 和 tokenSecret

  5.         ->pubKeyPins.Allow 加载用于检验 CA 证书的 HASH 值

  6.         ->buildInsecureBootstrapKubeConfig (用 token-bootstrap-client 身份)获取信息

  7.            -> 从kube-public 获取 configmap (和 kubectl describe configmap cluster-info -n kube-public 中的信息是一致的)这个configmap 里面包含 JWS 签名和 master 的 CA 证书,获取对应 tokenID 的 jws token,验证 token 成功,并且验证 CA 的证书 hash,如果通过说明这个 master 是可信的,然后拿到 CA 证书以后开始构建自己的证书,建立 secure config。

  8.         ->


kubeconfigutil.WriteToDisk 会把 bootstrap-kubelet.conf 写入到配置目录中,kubeadm 的任务就完成了,之前的 kubeadm 会代替 kubelet 生成 kubelet.conf(其中用的是公钥鉴权),现在移走了,kubelet 启动的时候会尝试使用这个配置文件建立 HTTPS 的鉴权配置文件。 



可以看一下 kubelet 用这个配置给 master 发 CSR 以后得到了什么,可以看到是生成了自己的证书的。 



并且这个证书里面的 SAN 也是用来进行 Node 鉴权的身份确认的信息。用 openssl x509 -in /var/lib/kubelet/pki/kubelet-client.crt -text -noout查看信息。 



红线部分就是 Node 鉴权的信息,基于这个确认 node 的身份。另外一个是 Role Based Access Control,那个主要是配给 pod,限制 pod 行为用的,类似于 linux 的 chmod。

kubeadm 高可用配置

如果没有安全配置,其实高可用挺好配置的,现在加入了安全配置,虽然麻烦,但是还是能理解 k8s 的良苦用心的,理解了整个鉴权的过程以后就可以做,现在支持配置文件初始化其实更好配置了。可以基于这个文档配置,算了我还是不总结了,看懂上面的,照着配置就好了。主要是自己生成 CA,他们用的 cfssl 和 cfssljson,这个工具比 openssl 好用一点,比较容易配置。生成 ca 证书和私钥以后,就可以构建 HTTPS 的 etcd。


先创建 ca 的配置文件 ca-config.json。


  1. {

  2.    "signing": {

  3.        "default": {

  4.            "expiry": "43800h"

  5.        },

  6.        "profiles": {

  7.            "server": {

  8.                "expiry": "43800h",

  9.                "usages": [

  10.                    "signing",

  11.                    "key encipherment",

  12.                    "server auth",

  13.                    "client auth"

  14.                ]

  15.            },

  16.            "client": {

  17.                "expiry": "43800h",

  18.                "usages": [

  19.                    "signing",

  20.                    "key encipherment",

  21.                    "client auth"

  22.                ]

  23.            },

  24.            "peer": {

  25.                "expiry": "43800h",

  26.                "usages": [

  27.                    "signing",

  28.                    "key encipherment",

  29.                    "server auth",

  30.                    "client auth"

  31.                ]

  32.            }

  33.        }

  34.    }

  35. }


然后生成用于自签名的 csr 的配置文件 ca-csr.json,用于签发自签名的 CA 证书。


  1. {

  2.    "CN": "etcd",

  3.    "key": {

  4.        "algo": "rsa",

  5.        "size": 2048

  6.    }

  7. }


结果就是目录下面生成了,ca-key.pem 和 ca.pem,这个命令不太按规则,对应的叫 ca.key 和 ca.crt,pem 是密钥保存的格式。


接下来用 client.json 获得自己的私钥和通过 ca 签的证书。


  1. {

  2.    "CN": "client",

  3.    "key": {

  4.        "algo": "ecdsa",

  5.        "size": 256

  6.    }

  7. }


执行 cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client,生成了 client-key.pem,client.pem,分别是私钥和证书,把文件 ca.pem,ca-key.pem, client.pem,client-key.pem,ca-config.json, 拷贝到每台 master 机器上面,一般放到 /etc/kubernetes/pki/ 下面。 然后


  1. cfssl print-defaults csr > config.json

  2. sed -i '0,/CN/{s/example\.net/'"$PEER_NAME"'/}' config.json

  3. sed -i 's/www\.example\.net/'"$PRIVATE_IP"'/' config.json

  4. sed -i 's/example\.net/'"$PEER_NAME"'/' config.json

  5. cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server config.json | cfssljson -bare server

  6. cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer config.json | cfssljson -bare peer


签出出两对密钥和证书,把每台的机器的 域名 和 IP 替换掉示例的 example 配置,就是这些地方可以改成自己的域名和地址,这个会被用来 check。 



可以放到 kubelet 的 static pod 里面,启动 etcd,两个证书分别是用作 server 验证和 client 验证的,server 让别人访问的时候相信你,peer 是你访问别人的时候让别人相信你。


  1. cat >/etc/kubernetes/manifests/etcd.yaml <<EOF

  2. apiVersion: v1

  3. kind: Pod

  4. metadata:

  5. labels:

  6.    component: etcd

  7.    tier: control-plane

  8. name: <podname>

  9. namespace: kube-system

  10. spec:

  11. containers:

  12. - command:

  13.    - etcd --name ${PEER_NAME} \

  14.    - --data-dir /var/lib/etcd \

  15.    - --listen-client-urls https://${PRIVATE_IP}:2379 \

  16.    - --advertise-client-urls https://${PRIVATE_IP}:2379 \

  17.    - --listen-peer-urls https://${PRIVATE_IP}:2380 \

  18.    - --initial-advertise-peer-urls https://${PRIVATE_IP}:2380 \

  19.    - --cert-file=/certs/server.pem \

  20.    - --key-file=/certs/server-key.pem \

  21.    - --client-cert-auth \

  22.    - --trusted-ca-file=/certs/ca.pem \

  23.    - --peer-cert-file=/certs/peer.pem \

  24.    - --peer-key-file=/certs/peer-key.pem \

  25.    - --peer-client-cert-auth \

  26.    - --peer-trusted-ca-file=/certs/ca.pem \

  27.    - --initial-cluster etcd0=https://<etcd0-ip-address>:2380,etcd1=https://<etcd1-ip-address>:2380,etcd2=https://<etcd2-ip-address>:2380 \

  28.    - --initial-cluster-token my-etcd-token \

  29.    - --initial-cluster-state new

  30.    image: k8s.gcr.io/etcd-amd64:3.1.10

  31.    livenessProbe:

  32.    httpGet:

  33.        path: /health

  34.        port: 2379

  35.        scheme: HTTP

  36.    initialDelaySeconds: 15

  37.    timeoutSeconds: 15

  38.    name: etcd

  39.    env:

  40.    - name: PUBLIC_IP

  41.    valueFrom:

  42.        fieldRef:

  43.        fieldPath: status.hostIP

  44.    - name: PRIVATE_IP

  45.    valueFrom:

  46.        fieldRef:

  47.        fieldPath: status.podIP

  48.    - name: PEER_NAME

  49.    valueFrom:

  50.        fieldRef:

  51.        fieldPath: metadata.name

  52.    volumeMounts:

  53.    - mountPath: /var/lib/etcd

  54.    name: etcd

  55.    - mountPath: /certs

  56.    name: certs

  57. hostNetwork: true

  58. volumes:

  59. - hostPath:

  60.    path: /var/lib/etcd

  61.    type: DirectoryOrCreate

  62.    name: etcd

  63. - hostPath:

  64.    path: /etc/kubernetes/pki/etcd

  65.    name: certs

  66. EOF


然后每个节点的 master init 的配置文件按照下面这个配置,client.pem 是用来让 etcd 相信 apiserver 的。private-ip 和 load-balancer-ip 都是要写到证书的 SAN 里面的,不然用这些 ip 是访问不了 apiserver 的。


  1. apiVersion: kubeadm.k8s.io/v1alpha1

  2. kind: MasterConfiguration

  3. api:

  4.  advertiseAddress: <private-ip>

  5. etcd:

  6.  endpoints:

  7.  - https://<etcd0-ip-address>:2379

  8.  - https://<etcd1-ip-address>:2379

  9.  - https://<etcd2-ip-address>:2379

  10.  caFile: /etc/kubernetes/pki/etcd/ca.pem

  11.  certFile: /etc/kubernetes/pki/etcd/client.pem

  12.  keyFile: /etc/kubernetes/pki/etcd/client-key.pem

  13. networking:

  14.  podSubnet: <podCIDR>

  15. apiServerCertSANs:

  16. - <load-balancer-ip>

  17. apiServerExtraArgs:

  18.  apiserver-count: "3"

  19. EOF


至于怎么做 loadbalance 可以用七层的也可以用四层的,七层把证书配到负载均衡服务上,四层的就不用,自己在裸机器上做可以用 vip + nginx 做一个四层的,也可以把证书放到 nginx 上做七层的,但是在云环境下,都不怎么支持自己配置 vip,需要用云厂商的 lb 服务,这个就看具体提供商的服务怎么配了。



(对Kubernetes感兴趣小伙伴可加入我们技术交流微信群,入群方式在公众号后台回复 “加群” 获取)

推荐阅读

Kubernetes全球含金量最高证书,详细CKA考试指南来袭!

Kubernetes v1.10+Keepalived HA集群墙内部署实践

Kubernetes 联合创始人:K8S 的未来是 Serverless

运维万台Docker服务器,世界级浏览器IT架构之旅

Kubernetes HA 1.9 高可用集群,本地离线部署

IBM微讲堂出品 | Kubernetes 内功修炼实战

说说Kubernetes是怎么来的,又是怎么没的

漫画:小黄人学 Kubernetes Service

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存