# ConfigMap

前面的课程中我们学习了`Servie`的使用，`Service`是`Kubernetes`系统中非常重要的一个核心概念，我们还会在后面
的课程中继续学习`Service`的一些使用方法的。这节课我们来学习另外一个非常重要的资源对象：`ConfigMap`，我们知道许多应用经常会有从配置文件、命令行参数或者环境变量中读取一些配置信息，这些配置信息我们肯定不会直接写死到应用程序中去的，比如你一个应用连接一个`redis`服务，下一次想更换一个了的，还得重新去修改代码，重新制作一个镜像，这肯定是不可取的，而`ConfigMap`就给我们提供了向容器中注入配置信息的能力，不仅可以用来保存单个属性，也可以用来保存整个配置文件，比如我们可以用来配置一个`redis`服务的访问地址，也可以用来保存整个`redis`的配置文件。


## 创建

`ConfigMap` 资源对象使用`key-value`形式的键值对来配置数据，这些数据可以在`Pod`里面使用，`ConfigMap`和我们后面要讲到的`Secrets`比较类似，一个比较大的区别是`ConfigMap`可以比较方便的处理一些非敏感的数据，比如密码之类的还是需要使用`Secrets`来进行管理。我们来举个例子说明下`ConfigMap`的使用方法：

```yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: cm-demo
  namespace: default
data:
  data.1: hello
  data.2: world
  config: |
    property.1=value-1
    property.2=value-2
    property.3=value-3
```

其中配置数据在`data`属性下面进行配置，前两个被用来保存单个属性，后面一个被用来保存一个配置文件。

当然同样的我们可以使用`kubectl create -f xx.yaml`来创建上面的`ConfigMap`对象，但是如果我们不知道怎么创建`ConfigMap`的话，不要忘记`kubectl`是我们最好的老师，可以使用`kubectl create configmap -h`来查看关于创建`ConfigMap`的帮助信息，
```
Examples:
  # Create a new configmap named my-config based on folder bar
  kubectl create configmap my-config --from-file=path/to/bar
  
  # Create a new configmap 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 configmap named my-config with key1=config1 and key2=config2
  kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
```

我们可以看到可以从一个给定的目录来创建一个`ConfigMap`对象，比如我们有一个`testcm`的目录，该目录下面包含一些配置文件，`redis`和`mysql`的连接信息，如下：
```shell
$ ls testcm
redis.conf
mysql.conf

$ cat testcm/redis.conf
host=127.0.0.1
port=6379

$ cat testcm/mysql.conf
host=127.0.0.1
port=3306
```

然后我们可以使用`from-file`关键字来创建包含这个目录下面所以配置文件的`ConfigMap`：
```shell
$ kubectl create configmap cm-demo1 --from-file=testcm
configmap "cm-demo1" created
```
其中`from-file`参数指定在该目录下面的所有文件都会被用在`ConfigMap`里面创建一个键值对，键的名字就是文件名，值就是文件的内容。

创建完成后，同样我们可以使用如下命令来查看`ConfigMap`列表：
```shell
$ kubectl get configmap
NAME       DATA      AGE
cm-demo1   2         17s
```
可以看到已经创建了一个`cm-demo1`的`ConfigMap`对象，然后可以使用`describe`命令查看详细信息：
```shell
kubectl describe configmap cm-demo1
Name:         cm-demo1
Namespace:    default
Labels:       <none>
Annotations:  <none>

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

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

Events:  <none>
```
我们可以看到两个`key`是`testcm`目录下面的文件名称，对应的`value`值的话就是文件内容，这里值得注意的是如果文件里面的配置信息很大的话，`describe`的时候可能不会显示对应的值，要查看键值的话，可以使用如下命令：
```shell
$ kubectl get configmap cm-demo1 -o yaml
apiVersion: v1
data:
  mysql.conf: |
    host=127.0.0.1
    port=3306
  redis.conf: |
    host=127.0.0.1
    port=6379
kind: ConfigMap
metadata:
  creationTimestamp: 2018-06-14T16:24:36Z
  name: cm-demo1
  namespace: default
  resourceVersion: "3109975"
  selfLink: /api/v1/namespaces/default/configmaps/cm-demo1
  uid: 6e0f4d82-6fef-11e8-a101-525400db4df7
```

除了通过文件目录进行创建，我们也可以使用指定的文件进行创建`ConfigMap`，同样的，以上面的配置文件为例，我们创建一个`redis`的配置的一个单独`ConfigMap`对象：
```shell
$ kubectl create configmap cm-demo2 --from-file=testcm/redis.conf
configmap "cm-demo2" created
$ kubectl get configmap cm-demo2 -o yaml
apiVersion: v1
data:
  redis.conf: |
    host=127.0.0.1
    port=6379
kind: ConfigMap
metadata:
  creationTimestamp: 2018-06-14T16:34:29Z
  name: cm-demo2
  namespace: default
  resourceVersion: "3110758"
  selfLink: /api/v1/namespaces/default/configmaps/cm-demo2
  uid: cf59675d-6ff0-11e8-a101-525400db4df7
```
我们可以看到一个关联`redis.conf`文件配置信息的`ConfigMap`对象创建成功了，另外值得注意的是`--from-file`这个参数可以使用多次，比如我们这里使用两次分别指定`redis.conf`和`mysql.conf`文件，就和直接指定整个目录是一样的效果了。


另外，通过帮助文档我们可以看到我们还可以直接使用字符串进行创建，通过`--from-literal`参数传递配置信息，同样的，这个参数可以使用多次，格式如下：
```
$ kubectl create configmap cm-demo3 --from-literal=db.host=localhost --from-literal=db.port=3306
configmap "cm-demo3" created
$ kubectl get configmap cm-demo3 -o yaml
apiVersion: v1
data:
  db.host: localhost
  db.port: "3306"
kind: ConfigMap
metadata:
  creationTimestamp: 2018-06-14T16:43:12Z
  name: cm-demo3
  namespace: default
  resourceVersion: "3111447"
  selfLink: /api/v1/namespaces/default/configmaps/cm-demo3
  uid: 06eeec7e-6ff2-11e8-a101-525400db4df7
```

## 使用

`ConfigMap`创建成功了，那么我们应该怎么在`Pod`中来使用呢？我们说`ConfigMap`这些配置数据可以通过很多种方式在`Pod`里使用，主要有以下几种方式：

* 设置环境变量的值
* 在容器里设置命令行参数
* 在数据卷里面创建config文件

首先，我们使用`ConfigMap`来填充我们的环境变量：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: testcm1-pod
spec:
  containers:
    - name: testcm1
      image: busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: cm-demo3
              key: db.host
        - name: DB_PORT
          valueFrom:
            configMapKeyRef:
              name: cm-demo3
              key: db.port
      envFrom:
        - configMapRef:
            name: cm-demo1
```

这个Pod运行后会输出如下几行：
```shell
$ kubectl logs testcm1-pod
......
DB_HOST=localhost
DB_PORT=3306
mysql.conf=host=127.0.0.1
port=3306
redis.conf=host=127.0.0.1
port=6379
......
```
我们可以看到`DB_HOST`和`DB_PORT`都已经正常输出了，另外的环境变量是因为我们这里直接把`cm-demo1`给注入进来了，所以把他们的整个键值给输出出来了，这也是符合预期的。


另外我们可以使用`ConfigMap`来设置命令行参数，`ConfigMap`也可以被用来设置容器中的命令或者参数值，如下`Pod`:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: testcm2-pod
spec:
  containers:
    - name: testcm2
      image: busybox
      command: [ "/bin/sh", "-c", "echo $(DB_HOST) $(DB_PORT)" ]
      env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: cm-demo3
              key: db.host
        - name: DB_PORT
          valueFrom:
            configMapKeyRef:
              name: cm-demo3
              key: db.port
```

运行这个`Pod`后会输出如下信息：
```shell
$ kubectl logs testcm2-pod
localhost 3306
```

另外一种是非常常见的使用`ConfigMap`的方式：通过数据卷使用，在数据卷里面使用`ConfigMap`，就是将文件填入数据卷，在这个文件中，键就是文件名，键值就是文件内容：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: testcm3-pod
spec:
  containers:
    - name: testcm3
      image: busybox
      command: [ "/bin/sh", "-c", "cat /etc/config/redis.conf" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: cm-demo2
```

运行这个`Pod`的，查看日志：
```shell
$ kubectl logs testcm3-pod
host=127.0.0.1
port=6379
```

当然我们也可以在`ConfigMap`值被映射的数据卷里去控制路径，如下`Pod`定义：
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: testcm4-pod
spec:
  containers:
    - name: testcm4
      image: busybox
      command: [ "/bin/sh","-c","cat /etc/config/path/to/msyql.conf" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: cm-demo1
        items:
        - key: mysql.conf
          path: path/to/msyql.conf
```

运行这个`Pod`的，查看日志：
```shell
$ kubectl logs testcm4-pod
host=127.0.0.1
port=3306
```


另外需要注意的是，当`ConfigMap`以数据卷的形式挂载进`Pod`的时，这时更新`ConfigMap`（或删掉重建`ConfigMap`），`Pod`内挂载的配置信息会热更新。这时可以增加一些监测配置文件变更的脚本，然后`reload`对应服务。



---
[点击查看本文视频](https://youdianzhishi.com/course/6n8xd6/)

扫描下面的二维码(或微信搜索`k8s技术圈`)关注我们的微信公众帐号，在微信公众帐号中回复 **加群** 即可加入到我们的 kubernetes 讨论群里面共同学习。

![k8s技术圈二维码](https://www.qikqiak.com/img/posts/qrcode_for_gh_d6dd87b6ceb4_430.jpg)
