3a613c244a46904c51a2a25f2280975d.png

总结

  1. 在pod definition中可以override本身的命令行启动命令
  2. 传递命令行参数给容器主进程
  3. 给容器设定环境变量
  4. 配置与pod通过configmap解耦
  5. 用sercret来存储机信息
  6. 使用docker-registry secret来从私人registry中抽镜像。

开始说CM之前,一般来说我们可以通过以下方法来往Docker里传参数:

  • 传递命令行参数
  • 给每个docker环境配置环境变量
  • 把配置文件mount到docker里的特定位置。

开始k8s之前,我们可以先看看上面说的几种方式是如何实现的

传递命令行参数

这个简单$ docker run <image> <arguments>

我们可以通过以下两种方式来启动image

  • shell form—For example, ENTRYPOINT node app.js.
  • exec form—For example, ENTRYPOINT ["node", "app.js"].

这两种方式的区别,我们举个例子:

如果通过ENTRYPOINT的方式来运行:

ENTRYPOINT ["node", "app.js"]

这将会直接泡在节点进程中(而非shell里),我们可以ps下看看

$ docker exec 4675d ps x
  PID TTY      STAT   TIME COMMAND
    1 ?        Ssl    0:00 node app.js
   12 ?        Rs     0:00 ps x

如果我们使用shell的方式来执行,那么命令就会存在于container的process中

$ docker exec -it e4bad ps x
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c node app.js
    7 ?        Sl     0:00 node app.js
   13 ?        Rs+    0:00 ps x

我们可以发现pid=1的是shell进程,但实际上pid=7的节点进程也在跑。所以shell进程就显得有些多余,所以我们应该总是使用exec的方式来执行ENTRYPOINT命令

下面是书上一个创建delay sleep发祝福语的例子,先创建bash脚本

#!/bin/bash
trap "exit" SIGINT
INTERVAL=$1
echo Configured to generate new fortune every $INTERVAL seconds
mkdir -p /var/htdocs
while :
do
  echo $(date) Writing fortune to /var/htdocs/index.html
  /usr/games/fortune > /var/htdocs/index.html
  sleep $INTERVAL
done

然后创建一个Dockerfile

FROM ubuntu:latest
RUN apt-get update ; apt-get -y install fortune
ADD fortuneloop.sh /bin/fortuneloop.sh
ENTRYPOINT ["/bin/fortuneloop.sh"]                 ❶
CMD ["10"]                                         ❷
  • The exec form of the ENTRYPOINT instruction
  • The default argument for the executable

然后创建image

$ docker build -t docker.io/luksa/fortune:args .
$ docker push docker.io/luksa/fortune:args

现在当我们不给参数时候,默认的环境变量值是10

$ docker run -it docker.io/luksa/fortune:args
Configured to generate new fortune every 10 seconds
Fri May 19 10:39:44 UTC 2017 Writing fortune to /var/htdocs/index.html

当然我们也可以传递参数来改变

$ docker run -it docker.io/luksa/fortune:args 15
Configured to generate new fortune every 15 seconds

在k8s上覆盖命令和命令行参数

在k8s中,我们可以是可以把ENTRYPOINT和CMD的参数覆盖的,方法如下:

kind: Pod
spec:
  containers:
  - image: some/image
    command: ["/bin/command"]
    args: ["arg1", "arg2", "arg3"]

一般来说我们只会设定特定的参数,很少会真的覆盖命令,下面是一个对比图:

bd1a41fdffc1d3041f110043cefe8836.png

拿刚刚那个延迟发祝福语的镜像举个例子:

apiVersion: v1
kind: Pod
metadata:
  name: fortune2s                    ❶
spec:
  containers:
  - image: luksa/fortune:args        ❷
    args: ["2"]                      ❸
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
...
  • ❶ You changed the pod’s name.
  • Using fortune:args instead of fortune:latest
  • This argument makes the script generate a new fortune every two seconds.

别忘了YAML也支持下面的方式写个数组:

    args:
    - foo
    - bar
    - "15"
字符串不要"",但是数值要

给container设定环境变量

k8s支持直接在配置文件中设置环境变量,当然这么做的话环境变量的生命周期与pod一样是不可变的

8dcfc183210dc1a9855453dde1dc54ea.png

这样我们就不用声明INTERVAL=$1了,脚本可以直接改成:

#!/bin/bash
trap "exit" SIGINT
echo Configured to generate new fortune every $INTERVAL seconds
mkdir -p /var/htdocs
while :
do
  echo $(date) Writing fortune to /var/htdocs/index.html
  /usr/games/fortune > /var/htdocs/index.html
  sleep $INTERVAL
done
这里书里也很贴心的写到:If the app was written in Java you’d useSystem.getenv("INTERVAL"), whereas in Node.JS you’d useprocess.env.INTERVAL, and in Python you’d useos.environ['INTERVAL']

然后再pod yaml中声明环境变量:

kind: Pod
spec:
 containers:
 - image: luksa/fortune:env
   env:                            ❶
   - name: INTERVAL                ❶
     value: "30"                   ❶
   name: html-generator
...

Adding a single variable to the environment variable list

注意:这里的环境变量是container级别的而不是pod级别的

在文件中的环境变量是支持互相引用的

env:
- name: FIRST_VAR
  value: "foo"
- name: SECOND_VAR
  value: "$(FIRST_VAR)bar"

hardcode不是一种优雅的方式。我们对于不同环境上的pod可能会有不同的配置,但是pod本身可能是同一个,这就需要将配置与pod进行结构,所以就需要config map.

正式介绍Configmaps

k8s支持将配置文件抽象成一个分离的对象,这个对象可以使一些键值对,也可以是一整个配置文件。

一个应用都不需要知道cm存在,一般来说cm的内容是通过环境变量或者配置文件的形式传入pods中的。

8690e2bc8a8b263cf5f34ecca097b05f.png

这样我们就可以在不同的环境中引用不同的环境变量来配置我们的pod

2be5e4df2555ae4355812644ecf07211.png

如何创建ConfigMap

最简单的方法是直接通过命令行:

$ kubectl create configmap fortune-config --from-literal=sleep-interval=25
configmap "fortune-config" created
ConfigMap keys must be a valid DNS subdomain (they may only contain alphanumeric characters, dashes, underscores, and dots). They may optionally include a leading dot.

创建后的示意图如下:

a0ee9e4c27e4502f7abd86161a4c3d01.png

实际上config的输入项往往不止一个,多个参数可以如下创建:

$ kubectl create configmap myconfigmap
➥   --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two

我们可以把刚刚创建出来的config拉出来看看长什么样:

$ kubectl get configmap fortune-config -o yaml
apiVersion: v1
data:
  sleep-interval: "25"                                  ❶
kind: ConfigMap                                         ❷
metadata:
  creationTimestamp: 2016-08-11T20:31:08Z
  name: fortune-config                                  ❸
  namespace: default
  resourceVersion: "910025"
  selfLink: /api/v1/namespaces/default/configmaps/fortune-config
  uid: 88c4167e-6002-11e6-a50d-42010af00237
  • The single entry in this map
  • This descriptor describes a ConfigMap.
  • The name of this map (you’re referencing it by this name)

写起来不难,所以也可以直接通过yaml把文件提交到api创建

$ kubectl create -f fortune-config.yaml

kubectl也支持直接从文件中读取配置

$ kubectl create configmap my-config --from-file=config-file.conf

如此k8s会把本地当前文件夹下的config-file.conf文件配置成环境变量,config.file为键,文件内容为值。如果我们想主动配置键的话,可以改成

$ kubectl create configmap my-config --from-file=customkey=config-file.conf

这样就可以把键换成自己想要的了。

我们也可以一次性导入多个文件配置CM

$ kubectl create configmap my-config
➥   --from-file=foo.json                     ❶
➥   --from-file=bar=foobar.conf              ❷
➥   --from-file=config-opts/                 ❸
➥   --from-literal=some=thing                ❹
  • A single file
  • A file stored under a custom key
  • A whole directory
  • ❹ A literal value

每个文件导入后与k8s的交互示意图如下:

95f6ffe2da7fec42bebb87cc22a0cf5e.png

将ConfigMap作为环境变量传入:

apiVersion: v1
kind: Pod
metadata:
  name: fortune-env-from-configmap
spec:
  containers:
  - image: luksa/fortune:env
    env:                             ❶
    - name: INTERVAL                 ❶
      valueFrom:                     ❷
        configMapKeyRef:             ❷
          name: fortune-config       ❸
          key: sleep-interval        ❹
  • ❶ You’re setting the environment variable called INTERVAL.
  • Instead of setting a fixed value, you’re initializing it from a ConfigMap key.
  • The name of the ConfigMap you’re referencing
  • You’re setting the variable to whatever is stored under this key in the ConfigMap.

这就是一个从configmap中读取环境变量的例子,示意图如下:

3e28a2edf57ae3d99e85c338a4c1d961.png
如果指定的环境变量不存在,pod会启动失败。当然如果你把config补上了,pod就会自动重新启动。You can also mark a reference to a ConfigMap as optional (by settingconfigMapKeyRef.optional: true). In that case, the container starts even if the ConfigMap doesn’t exist.

一次性传入所有的configmap中的环境变量

写多了就麻烦,一次性传入所有的环境变量就可以写成:

spec:
  containers:
  - image: some-image
    envFrom:                      ❶
    - prefix: CONFIG_             ❷
      configMapRef:               ❸
        name: my-config-map       ❸
...
  • Using envFrom instead of env
  • All environment variables will be prefixed with CONFIG_.
  • Referencing the ConfigMap called my-config-map
prefix 不是必须的,如果没有则变量名与config中就会一致。

另外需要注意的是:

Did you notice I said two variables, but earlier, I said the ConfigMap has three entries (FOO, BAR, and FOO-BAR)? Why is there no environment variable for the FOO-BAR ConfigMap entry?
The reason is that CONFIG_FOO-BAR isn’t a valid environment variable name because it contains a dash. Kubernetes doesn’t convert the keys in any way (it doesn’t convert dashes to underscores, for example). If a ConfigMap key isn’t in the proper format, it skips the entry (but it does record an event informing you it skipped it).

将configmap作为命令行参数输入:

下面来看个怎么把cm的变量传入container主进程的例子。

0f33436cda11aa39ee84e6ccebefdeaa.png

配置:

apiVersion: v1
kind: Pod
metadata:
  name: fortune-args-from-configmap
spec:
  containers:
  - image: luksa/fortune:args          ❶
    env:                               ❷
    - name: INTERVAL                   ❷
      valueFrom:                       ❷
        configMapKeyRef:               ❷
          name: fortune-config         ❷
          key: sleep-interval          ❷
    args: ["$(INTERVAL)"]              ❸
...
  • Using the image that takes the interval from the first argument, not from an environment variable
  • Defining the environment variable exactly as before
  • Referencing the environment variable in the argument

将config map作为文件输入

短变量通过刚刚的办法可以搞定,对于较长的配置文件k8s也是可以支持的。当我们想用configmap来提供配置文件时,我们可以用一种特殊的volume - configMapvolume.

随便写个nginx配置文件

server {
  listen              80;
  server_name         www.kubia-example.com;

  gzip on;                                       ❶
  gzip_types text/plain application/xml;         ❶

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
}

This enables gzip compression for plain text and XML files.

创建一个文件夹,包含下面2个文件以及参数:

406bf93282faf0347268615e8e487933.png

然后创建一个configmap

$ kubectl create configmap fortune-config --from-file=configmap-files
configmap "fortune-config" created

看看yaml长啥样

$ kubectl get configmap fortune-config -o yaml
apiVersion: v1
data:
  my-nginx-config.conf: |                            ❶
    server {                                         ❶
      listen              80;                        ❶
      server_name         www.kubia-example.com;     ❶

      gzip on;                                       ❶
      gzip_types text/plain application/xml;         ❶

      location / {                                   ❶
        root   /usr/share/nginx/html;                ❶
        index  index.html index.htm;                 ❶
      }                                              ❶
    }                                                ❶
  sleep-interval: |                                  ❷
    25                                               ❷
kind: ConfigMap
The pipeline character after the colon in the first line of both entries signals that a literal multi-line value follows.

配置文件有了我们可以看看怎么挂载到pod里。拿nginx举例,他会从etc/nginx/nginx.conf读取配置。我们可以加一个conf.d(为了避免覆盖原有的配置)

f7bd41385a47cd89b1c195e23e4ef4d6.png

配置写法如下:

apiVersion: v1
kind: Pod
metadata:
  name: fortune-configmap-volume
spec:
  containers:
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    ...
    - name: config
      mountPath: /etc/nginx/conf.d      ❶
      readOnly: true
    ...
  volumes:
  ...
  - name: config
    configMap:                          ❷
      name: fortune-config              ❷
  • ❶ You’re mounting the configMap volume at this location.
  • The volume refers to your fortune-config ConfigMap.

我们可以看看配置有没有生效,文件有没有生成

$ kubectl exec fortune-configmap-volume -c web-server ls /etc/nginx/conf.d
my-nginx-config.conf
sleep-interval
都有了

刚刚把整个config map都挂载了上去,我们也可以只挂载一个配置文件(通过选择key)

  volumes:
  - name: config
    configMap:
      name: fortune-config
      items:                             ❶
      - key: my-nginx-config.conf        ❷
        path: gzip.conf                  ❸
  • Selecting which entries to include in the volume by listing them
  • You want the entry under this key included.
  • The entry’s value should be stored in this file.
When specifying individual entries, you need to set the filename for each individual entry, along with the entry’s key. If you run the pod from the previous listing, the /etc/nginx/conf.d directory is kept nice and clean, because it only contains the gzip.conf file and nothing else.]

另一个需要注意的是,我们都通过dir的方式挂载了configmap,这样会把所有当前此镜像内的文件隐藏(挂载了新的volume), 那么我们就需要一种方法可以通过configmap增量的向文件夹中添加配置文件。

当然是可以的,通过volumeMountsubPath 属性就可以挂载一个单独的文件或者文件夹,让我们看个示意图:

4d76ab0066407dc187d226506ec89030.png

如果你有一个myconfig.conf配置文件希望挂载到/etc下,我们可以这么写配置文件:

spec:
  containers:
  - image: some/image
    volumeMounts:
    - name: myvolume
      mountPath: /etc/someconfig.conf      ❶
      subPath: myconfig.conf               ❷
  • You’re mounting into a file, not a directory.
  • Instead of mounting the whole volume, you’re only mounting the myconfig.conf entry.

默认来说configmap的权限都是644 (-rw-r—r--),我们可以通过defaultMode这个属性来进行配置。

  volumes:
  - name: config
    configMap:
      name: fortune-config
      defaultMode: "6600"           ❶

This sets the permissions for all files to -rw-rw-----.

无需重启pod更新配置文件

使用configmap的另一个好处是,当我们更新了config的值后,无需更新pod,他会自动更新所有对应的volume和config. 当然目前耗时会比较长,书里写的得一分钟才能生效。

使用SECRET 向容器传敏感信息

secret也是通过键值对存储数据,他们和configmap很像,只不过用来存保密信息?

  1. 只有需要用到secret的pod才会读取secret文件
  2. secret文件不会写入硬盘,永远在内存中

我们应该注意到我们使用kubectl describe来描述一个pod时,我们的输出总会包含:

Volumes:
  default-token-cfee9:
    Type:       Secret (a volume populated by a Secret)
    SecretName: default-token-cfee9

每个pod都会有一个secret volume自动的挂载上去。 secret也算是服务,我们可以通过get方法来看

$ kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-cfee9   kubernetes.io/service-account-token   3         39d

我们也可以通过describe让他多显示一些信息

$ kubectl describe secrets
Name:        default-token-cfee9
Namespace:   default
Labels:      <none>
Annotations: kubernetes.io/service-account.name=default
             kubernetes.io/service-account.uid=cc04bb39-b53f-42010af00237
Type:        kubernetes.io/service-account-token

Data
====
ca.crt:      1139 bytes                                   ❶
namespace:   7 bytes                                      ❶
token:       eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...      ❶

❶ This secret contains three entries.

你可以看到有三个文件:ca.crt,namespace, token 。有了这三个配置我们就可以安全的与k8s api通信。

每个pod在启动的时候都会自动挂载default-token的secret。

fc80b85ede5394bb28e0831382b44528.png

如何创建一个secret

创建一个2值的sercret 文件

$ openssl genrsa -out https.key 2048
$ openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj
     /CN=www.kubia-example.com
$ echo bar > foo

$ kubectl create secret generic fortune-https --from-file=https.key
➥   --from-file=https.cert --from-file=foo
secret "fortune-https" created

我们可以看看sercrt和cm的区别

$ kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
  foo: YmFyCg==
  https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCekNDQ...
  https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcE...
kind: Secret
...

以及

$ kubectl get configmap fortune-config -o yaml
apiVersion: v1
data:
  my-nginx-config.conf: |
    server {
      ...
    }
  sleep-interval: |
    25
kind: ConfigMap
...

可以发现config内的文件是加密b64加密的,我们可以用sercret来存非加密的数据,但需要注意的是每个sercret的最大上限是1MB。

当我们从secret中拿到数据并且往文件里写入时,数据会被解密。

如何在配置中使用sercret

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
  - image: luksa/fortune:env
    name: html-generator
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
    - name: certs                         ❶
      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                            ❷
    secret:                                ❷
      secretName: fortune-https            ❷
  • ❶ You configured Nginx to read the cert and key file from /etc/nginx/certs, so you need to mount the Secret volume there.
  • You define the secret volume here, referring to the fortune-https Secret.

下面 是个示意图:

45f9bde83bab71351608ee7c551791a7.png

使用secret的值作为环境变量

    env:
    - name: FOO_SECRET
      valueFrom:                   ❶
        secretKeyRef:              ❶
          name: fortune-https      ❷
          key: foo                 ❸
  • The variable should be set from the entry of a Secret.
  • The name of the Secret holding the key
  • The key of the Secret to expose
Think twice before using environment variables to pass your Secrets to your container, because they may get exposed inadvertently. To be safe, always use secret volumes for exposing Secrets.

通过sercret来从私有化docker中抽容器

如果想要从一个私有化的registory中抽取容器,我们需要:

  1. 创建一个secret账号
  2. 在pod manifest中制定imagePullSecrets

首先来创建secret

$ kubectl create secret docker-registry mydockerhubsecret 
  --docker-username=myusername --docker-password=mypassword 
  --docker-email=my.email@provider.com

这里注意的我们创建的就不是generic的sercret了,而是一个docker-registry的sercret。我们会声明docker里的用户名,密码,email。

接下来为了使这个配置生效,我们在配置pod的时候可以这么写配置

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:                 ❶
  - name: mydockerhubsecret         ❶
  containers:
  - image: username/private:tag
    name: main

This enables pulling images from a private image registry.

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐