7.5.1 介绍 Secret

Secret对象用于存储与分发配置中包含的敏感数据(如证书和私钥)。Secret结构与ConfigMap类似,均是键/值对的映射。Secret的使用方法也与ConfigMap相同,可以:

  • 将 Secret 条目作为环境变量传递给容器 (不建议使用)
  • 将 Secret 条目暴露为卷中的文件

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

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

Secret与ConfigMap的选择依据相对简单:

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

7.5.2 默认令牌 Secret 的介绍

对任意一个pod使用命令kubectl describe pod,输出往往包含以下信息:
image.png
每个pod都会被自动挂载上一个secret卷,这个卷引用的是图中的default-token-cfee9的secret。
( 因本人环境问题,输出不一样 )
image.png
(下面是我朋友虚拟机的输出,结果与示例匹配)
image.png

image.png
image.png

image.png
image.png
这个Secret包含三个条目——ca.crt、namespace、token,包含了从pod内部安全访问Kubernetes API Server所需的全部信息。

kubectl describe pod命令会显示secret卷被挂载的位置:
image.png
( 因环境问题,本人输出略有不同 )
image.png

注意:default-token Secret 默认会被挂载至每个容器。可以通过设置pod spec中的automountServiceAccountToken: false,或者设置pod使用的服务账户中的相同字段为false来关闭这种默认行为(后面会对服务账户进行讲解)。

通过kubectl exec观察被secret卷挂载的文件夹下包含三个文件:
image.png
image.png
image.png

7.5.3 创建Secret

实验:改进提供fortune服务的Nginx容器配置,使其能够服务于HTTPS流量。你需要创建私钥和证书,由于需要确保私服的安全性,可将其与证书同时存储Secret。

  1. mkdir -p /root/k8s/fortune-https
  2. cd /root/k8s/fortune-https
  3. #首先,需要创建私钥和证书:
  4. openssl genrsa -out https.key 2048
  5. openssl req -new -x509 -key https.key -out https.cert -days 360 -subj /CN=www.kubia-example.com
  6. #为了更好地理解Secret,额外创建一个内容为字符串的bar文件foo。
  7. echo "bar" >foo
  8. #用这3个文件创建一个Secret资源,名称为fortune-https
  9. kubectl create secret generic fortune-https --from-file=./

7.5.4 对比 ConfigMap 与 Secret

image.png
image.png
Secret条目的内容会被Base64格式编码,而ConfigMap直接以纯文本展示。在处理YAML和JSON格式的Secret,需要在设置和读取相关条目时对内容进行编码、解码。

什么是Base64?

https://baike.baidu.com/item/base64/8545775?fr=aladdin
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一。Base64就是一种基于64个可打印字符([A-Za-z0-9+/])来表示二进制数据的方法。Base64编码是从二进制到字符的过程。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(38 = 46 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

示例: 转换前 10101101,10111010,01110110 转换后 00101011, 00011011 ,00101001 ,00110110 十进制 43 27 41 54 对应码表中的值 rbp2

为二进制数据创建Secret

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

提示:Secrets甚至可以被用来存储非敏感二进制数据。不过一个Secret的大小限于 1MB

stringData字段介绍

Kubernetes允许通过Secret的stringData字段设置条目的纯文本值。
image.png
stringData字段是只写的(write-only),只能被用来设置条目值。通过kubctl get -o yaml 不会显示stringData字段。相反,stringData字段中的所有条目(比如上图的foo)会被Base64编码之后展示在data字段下。

在 pod 中读取Secret条目

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

7.5.5 在 pod 中使用 Secret

修改fortune-config ConfigMap以开启 HTTPS

kubectl edit configmap fortune-config
-------------------------------------------------
data:    #修改data字段,certs/是/etc/nginx的相对路径
  my-nginx-config.conf: |
    server {
        listen              80;
        listen              443 ssl;
        server_name         www.kubia-example.com;
        ssl_certificate     certs/https.cert;
        ssl_certificate_key certs/https.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers HIGH:!aNULL:!MD5;
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
    }
  sleep-interval: |
    25

挂载fortune-https secret至pod

cd /root/k8s/

cat >fortune-pod-https.yaml <<'EOF' 
apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
  - image: 10.0.0.10:5000/luksa/fortune:env
    name: html-generator
    env:
    - name: INTERVAL
      valueFrom: 
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: 10.0.0.10:5000/luksa/nginx
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
    - name: 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
EOF

kubectl create -f fortune-pod-https.yaml

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

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

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

image.png

Secret卷存储于内存

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

注意:挂载后,sercret卷中的文件已经解码(因为不解码Nginx无法使用文件)。secret卷挂载后在目录中暴露的文件也使用了符号链接。

通过环境变量暴露Secret条目(不建议使用)

将Secret中的键foo暴露为环境变量FOO_SECRET
image.png
Kubernetes允许通过环境变量暴露Secret,然而此特性的使用往往不是一个好主意。应用程序通常会在错误报告时转储 (dump) 环境变量,或者是启动时打印在应用日志中,无意中暴露了它们(要知道,Secret条目中保存的是敏感数据)。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,你并不知道它使用敏感数据做了什么。

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

了解镜像拉取(image pull) Secrets

Kubernetes自身在有些时候希望我们能够传递证书给它,比如从某个私有镜像仓库拉取镜像时。这一点同样需通过Secrets来做到。大部分组织机构不希望它们的镜像开放给所有人,因此会使用私有镜像仓库。部署 pod 时,如果容器镜像位于私有仓库, Kubernetes 需拥有拉取镜像所需的证书。

在Docke Hub上使用私有镜像仓库

登录http://hub.docker.com ,找到对应的镜像仓库,勾选指定的复选框,将仓库标记为私有。
运行一个镜像来源于私有仓库的pod时,需要做以下两件事:

  • 创建包含Docker镜像仓库证书的Secret。
  • pod定义中的 imagePullSecrets 字段引用该Secret。

创建用于Docker 镜像仓库鉴权的 Secret

image.png
这是创建了一个docker-registry类型的mydockerhubsecret Secret,创建时需指定Docker Hub用户名、密码、邮箱。
通过kubectl describe观察新Secret内容时会发现有一个条目 .dockercfg,,相当于用户主目录下的 ~/.dockercfg 文件(本人环境是~/.docker/config.json文件)。该文件通常在运行 docker login 命令时由Docker自动创建。
image.png

在pod定义中使用docker-registry Secret

image.png
该pod定义中,字段 imagePullSecrets 引用了 mydockerhubsecret Secret 。

不需要为每个 pod 指定 镜像拉取 Secret

假设某系统中通常运行大量pod,你可能会好奇是否需要为每个pod都添加相同的镜像拉取Secret。幸运的是,情况并非如此。第12章中将会学习到如何通过添加Secret至ServiceAccount使所有pod都能自动添加上image pull Secret。

7.5.6 实验:镜像拉取Secrets的Docker本地仓库registry实现

#安装启动registry (registry服务器10.0.0.10)
mkdir -p /opt/registry
docker image pull registry

#修改配置文件 (registry服务器,客户端)
vim /etc/docker/daemon.json
{
  "registry-mirrors": ["https://68rmyzg7.mirror.aliyuncs.com"], 
  "insecure-registries": ["10.0.0.10:5000"]  #加这行
}
systemctl restart docker


#本地仓库加安全认证 (registry服务器)
yum install -y httpd-tools
mkdir -p /opt/registry-auth/
htpasswd -Bbn "user" "password" >>/opt/registry-auth/htpasswd

#启动带有密钥功能的registry容器 (registry服务器)
docker container run -d -p 5000:5000 -v /opt/registry-auth/:/auth/ -v /opt/registry:/var/lib/registry  --name register-auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" registry

#push、pull镜像都需要进行login (registry服务器)
docker login 10.0.0.10:5000      #登录后密码记录在 ~/.docker/config.json
docker logout 10.0.0.10:5000

#创建用于Docker 镜像仓库鉴权的 Secret(注意docker-server要加端口,email随意)
kubectl create secret docker-registry myregistrysecret --docker-username=user --docker-password=password --docker-server=10.0.0.10:5000  --docker-email=my.email@163.com

#查看secret中的加密内容 
kubectl get secrets myregistrysecret --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d

#在pod定义中使用docker-registry Secret
cat >kubia-manual-with-private-image.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual
spec:
  containers:
  - image: 10.0.0.10:5000/luksa/kubia
    name: kubia
    ports:
    - containerPort: 8080
      protocol: TCP
  imagePullSecrets:
  - name: myregistrysecret       #引用 docker-registry Secret
EOF

kubectl create -f kubia-manual-with-private-image.yaml

注意:在创建pod后容器一直拉取镜像失败,提示no basic auth credentials。后来反复排查,确认是因为—docker-server没有指定本地仓库的端口 :5000。(也是因为我在指定image的时候加了端口)

registry开启安全认证后,在pod还未配置imagePullSecrets字段的时候,pod中的容器无法拉取镜像。
kubectl create -f kubia-manual.yaml
image.png
kubectl describe po kubia-manual
image.png
#配置imagePullSecrets重新创建pod,pod中的容器就能正常拉取镜像并运行了。
image.png