0

【K8S专栏】Kubernetes应用配置管理

 1 year ago
source link: https://blog.51cto.com/u_12970189/5682244
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

微信公众号:运维开发故事,作者:乔克

不论什么样的应用,基本都有配置文件,在企业中,大部分会用到配置中心,比如apollo、nacos等,也有一些公司直接使用Kubernetes自带的配置管理,主要有:

  • Secret
  • ConfigMap

Secret

如果把配置信息保存在Secret中,其会被加密存放到Etcd中,Pod可以通过以下两种种方式使用它:

  • 通过环境变量的方式
  • 通过挂载的方式
  • 指定拉取镜像的Secret

一般情况下,通过Secret保存的配置信息都是敏感信息,比如数据库的账号密码、认证服务的账号密码等,且Secret不宜过大,因为如果使用大的Secret,则将大量占用API Server和kubelet的内存。

创建Secret

创建Secret的方式主要有两种:

  • 使用YAML文件创建
  • 使用kubectl命令创建

使用YAML文件创建

使用YAML文件创建,就要熟悉Secret的配置详情,可以通过​​kubectl explain secret​​去查看。其主要字段有apiVersion,data,kind,metadata,type。

比如创建一个简单的Secret如下:

apiVersion: v1
kind: Secret
metadata:
name: my-secret-volume
type: Opaque
data:
user: cm9vdA==
password: UEBzc1cwcmQ=

其中apiVersion、kind和metadata是常用字段,这里就不赘述了。type表示secret的类型,主要有以下几种:

  • Qpaque:可以定义任意数据
  • kubernetes.io/service-account-token:配置ServiceAccount Token
  • kubernetes.io/dockercfg:配置docker认证文件
  • kubernetes.io/dockerconfigjson:配置docker认证文件
  • kubernetes.io/basic-auth:配置基础认证
  • kubernetes.io/ssh-auth:配置ssh认证
  • kubernetes.io/tls:配置TLS证书
  • bootstrap.kubernetes.io/token:配置bootstrap token

如果在创建Secret的时候没有指定类型,默认使用Qpaque类型。另外data的数据的值是需要base64转码。

使用kubectl命令创建

在使用kubectl创建的时候,如果不熟悉子命令信息,可以通过kubectl explain secret查看。

我们使用以下命令创建一个Secret:

$ kubectl create secret generic secret-auth-test --from-literal=username=joker --from-literal=password=123

创建完成后,可以看到username和password的值被自动加密了,如下:

$ kubectl get secrets secret-auth-test -oyaml
apiVersion: v1
data:
password: MTIz
username: am9rZXI=
kind: Secret
metadata:
creationTimestamp: "2022-T07:18Z"
name: secret-auth-test
namespace: default
resourceVersion: "652834"
uid: ff1b756a-6b38-4b68-a47c-c51988b68
type: Opaque

除了直接在命令行输入数据,还可以从文件创建,如下:

$ echo -n 'admin' > ./username.txt
$ echo -n '1f2d1e2e67df' > ./password.txt

然后通过--from-file引入文件,如下:

$ kubectl create secret generic db-user-pass \
--from-file=./username.txt \
--from-file=./password.txt

创建后的secret值都是加密的,如果要获取明文信息,通过以下命令即可:

$ kubectl get secret db-user-pass -o jsonpath='{.data.password}' | base64 --decode

默认情况下,secret是使用base64加密的,所以解密可以直接使用base64解密。

使用Secret

Secret只是一个静态资源,最终,我们是想使用它,在实际中,主要通过以下方式使用:

  • 通过环境变量的方式
  • 通过挂载的方式
  • 指定拉取镜像的Secret

我们在上面创建了​​secret-auth-test​​的Secret,下面分别使用以上三种方式进行使用。

通过环境变量使用Secret

在Pod的对象中,有spec.containers.env.valueFrom.secretKeyRef字段,该字段可以用来引用Secret字段,如下:

apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: mycontainer
image: redis
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: secret-auth-test
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: secret-auth-test
key: password

这样就会把Secret里的信息注入到容器环境变量里,应用可以直接通过读取环境变量来使用。

通过挂载的方式使用Secret

可以使用挂载的方式,将Secret以文件的形式挂载到容器中,如下:

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: secret-auth-test

这样就会把数据挂载到/etc/foo这个目录里,如下:

$ kubectl exec -it mypod -- /bin/sh
# ls -l /etc/foo
total 0
lrwxrwxrwx 1 root root 15 Jul 25 08:30 password -> ..data/password
lrwxrwxrwx 1 root root 15 Jul 25 08:30 username -> ..data/username

如果Secret里有多个键值,还可以只挂载某一个数据,如下:

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: secret-auth-test
items:
- key: username
path: my-group/my-username

上面指定volumes.secret.items.path用来指定username的子目录,如下:

$ kubectl exec -it mypod-password -- /bin/bash
root@mypod-password:/data# cat /etc/foo/my-group/my-username
joker

除此之外,还可以指定权限,如下:

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: secret-auth-test
defaultMode: 0400

然后可以看到被挂载的Secret的权限如下:

$ kubectl exec -it mypod-permision -- /bin/bash
root@mypod-permision:/etc/foo# ls -l
total 0
lrwxrwxrwx 1 root root 15 Jul 25 08:38 password -> ..data/password
lrwxrwxrwx 1 root root 15 Jul 25 08:38 username -> ..data/username
root@mypod-permision:/etc/foo# ls ..data/password -l
-r-------- 1 root root 3 Jul 25 08:38 ..data/password

注意:我们进/etc/foo目录直接使用ls -l查看到的权限是777,但是仔细的人可以发现其实质是一个链接文件,我们真正要看的权限是被链接的文件,也就是上面的..data/password。

在拉取镜像的时候使用Secret

我们在前面列举了很多YAML文件,都没有配置imagePullSecret,主要是那些镜像都是Dockerhub官方的镜像,对外是公开的。

然而,在实际的生产中,不会将自己公司的镜像对外公开,这非常的不安全。如果镜像仓库加密了,在下载镜像的时候要docker login,在Kubernetes中,也免不了该操作。

为此,Kubernetes提供了imagePullSecret字段,该字段用来指定拉取镜像的Secret,这个Secret会保存镜像仓库的认证信息。

(1)首先创建镜像认证信息的Secret

kubectl create secret \
docker-registry pull-registry-secret \
--docker-server=registry.test.cn \
--docker-username=ops \
--docker-password=ops123123 \

(2)在Pod中使用

apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
imagePullSecrets:
- name: pull-registry-secret
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: secret-auth-test
defaultMode: 0400

这样就可以拉取私有仓库里的镜像了。

综上,我们可以通过Secret保管其他系统的敏感信息(比如数据库的用户名和密码),并以Mount的方式将Secret挂载到Container中,然后通过访问目录中文件的方式获取该敏感信息。当Pod被API Server创建时,API Server不会校验该Pod引用的Secret是否存在。一旦这个Pod被调度,则kubelet将试着获取Secret的值。如果Secret不存在或暂时无法连接到API Server,则kubelet按一定的时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动的原因。一旦Secret被Pod获取,则kubelet将创建并挂载包含Secret的Volume。只有所有Volume都挂载成功,Pod中的Container才会被启动。在kubelet启动Pod中的Container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改。为了使用更新后的Secret,必须删除旧Pod,并重新创建一个新Pod。

ConfigMap

ConfigMap和Serect类似,不同之处在于ConfigMap保存的数据信息是不需要加密的,比如一些应用的配置信息,其他的用法和Secret一样。

创建ConfigMap

同样,我们可以使用两种方式来创建ConfigMap:

  • 通过命令行方式,也就是kubectl create configmap;
  • 通过YAML文件方式;

通过命令创建ConfigMap

如果不熟悉ConfigMap对象的字段,可以通过kubectl explain configmap来查看,如果想查看创建configmap的示例,可以通过kubectl create configmap -h查看,如下:

Examples:
# Create a new config map named my-config based on folder bar
kubectl create configmap my-config --from-file=path/to/bar

# Create a new config map named my-config with specified keys instead of file basenames on disk
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

# Create a new config map named my-config with key1=config1 and key2=config2
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

# Create a new config map named my-config from the key=value pairs in the file
kubectl create configmap my-config --from-file=path/to/bar

# Create a new config map named my-config from an env file
kubectl create configmap my-config --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env

从上面可以看出,创建ConfigMap可以从给定一个目录来创建。例如,我们定义了如下一些配置文件:

$ mkdir configmap-demo
$ cd configmap-demo
$ ll
total 8
-rw-r--r-- 1 root root 25 Sep 6 17:07 mysqld.conf
-rw-r--r-- 1 root root 25 Sep 6 17:07 redis.conf
$ cat mysqld.conf
host=127.0.0.1
port=3306
$ cat redis.conf
host=127.0.0.1
port=6379

然后使用一下命令来进行创建:

$ kubectl create configmap my-configmap --from-file=../configmap-demo/

然后通过一下命令查看创建完的configmap:

$ kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 21d
my-configmap 2 9s
$ kubectl describe cm my-configmap
Name: my-configmap
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
mysqld.conf:
----
host=127.0.0.1
port=3306

redis.conf:
----
host=127.0.0.1
port=6379


BinaryData
====

Events: <none>

我们可以看到两个key对应的是文件的名字,value对应的是文件的内容。如果要看键值的话可以通过如下命令查看:

$ kubectl get configmap my-configmap -o yaml
apiVersion: v1
data:
mysqld.conf: |
host=127.0.0.1
port=3306
redis.conf: |
host=127.0.0.1
port=6379
kind: ConfigMap
metadata:
creationTimestamp: "2022-07-25T09:20:43Z"
name: my-configmap
namespace: default
resourceVersion: "667706"
uid: 46cb52e9-0936-4934-9628-ac20efcfd893

当然,我们还可以通过文件来创建一个configmap,比如我们定义一个如下的配置文件:

$ cat nginx.conf
user nobody;
worker_processes 1;
error_log logs/error.log;
error_log logs/error.log notice;
error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

然后通过如下命令创建一个nginx的configmap:

$ kubectl create configmap nginx-configmap --from-file=nginx.conf

查看创建后的信息:

$ kubectl get configmap nginx-configmap -o yaml
apiVersion: v1
data:
nginx.conf: |
user nobody;
worker_processes 1;
error_log logs/error.log;
error_log logs/error.log notice;
error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
kind: ConfigMap
metadata:
creationTimestamp: "2022-07-25T09:24:29Z"
name: nginx-configmap
namespace: default
resourceVersion: "668283"
uid: a025da28-6817-4605-8daf-375b676282c1

注:在一条命令中--from-file可以指定多次。

另外,通过帮助文档我们可以看到我们还可以直接使用字符串进行创建,通过--from-literal参数传递配置信息,同样的,这个参数可以使用多次,格式如下:

$ kubectl create configmap my-cm-daemo --from-literal=db.host=localhost --from-literal=db.port=3306

通过YAML创建ConfigMap

通过YAML文件创建就比较简单,我们可以参考上面输出的yaml信息,比如定义如下一个YAML文件:

apiVersion: v1
kind: ConfigMap
metadata:
name: my-cm-daemon2
labels:
app: cm-daemon
data:
redis.conf: |
host=127.0.0.1
port=6379

然后创建即可。

使用ConfigMap

ConfigMap中的配置数据可以通过如下方式进行使用:

  • 设置环境变量值
  • 在数据卷中创建config文件

通过环境变量使用ConfigMap

我们直接通过在pod.spec.containers.env.valueFrom.configMapKeyRef中引用ConfigMap即可,如下:

apiVersion: v1
kind: Pod
metadata:
name: env-configmap
labels:
app: env-configmap-mysql
spec:
containers:
- name: test-configmap
image: busybox
command:
- "/bin/sh"
- "-c"
- "env"
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: my-cm-daemo
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: my-cm-daemo
key: db.port
envFrom:
- configMapRef:
name: my-cm-daemo

创建后,可以通过日志查看环境变量输出,如下:

$ kubectl logs env-configmap | grep DB
DB_PORT=3306
DB_HOST=localhost

通过数据卷使用ConfigMap

基本原理和Secret一样。

在这里,通过指定pod.spec.volumes.configMap.name来指定ConfigMap,然后挂载到容器里,如下:

apiVersion: v1
kind: Pod
metadata:
name: volume-configmap-test
spec:
containers:
- name: volume-configmap-test
image: busybox
command: [ "/bin/sh", "-c", "cat /etc/config/redis.conf" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: my-configmap

我们可以通过日志查看ConfigMap是否挂载进去了。

$ kubectl logs volume-configmap-test
host=127.0.0.1
port=6379

我们也可以在ConfigMap值被映射的数据卷里去控制路径,如下:

apiVersion: v1
kind: Pod
metadata:
name: volume-path-configmap
spec:
containers:
- name: volume-path-configmap-test
image: busybox
command: [ "/bin/sh","-c","cat /etc/config/path/to/msyqld.conf" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: my-configmap
items:
- key: mysqld.conf
path: path/to/msyqld.conf

另外,当ConfigMap以数据卷的形式挂载进Pod的时,这时更新ConfigMap(或删掉重建ConfigMap),Pod内挂载的配置信息会热更新。虽然配置信息更新,应用到底能不能使用,主要还是依赖应用是否也会热更新。

ConfigMap在实际中用的还是比较多,主要都是一些应用的配置文件,比如Nginx配置文件,MySQL配置文件,这类配置文件如果想放到私有的配置中心需要额外花费更多的精力,而放到ConfigMap,则方便很多,而且多数都以挂载的方式放进容器里。

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。


我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK