backend/kubernetes in action

[kubernetes in action] 7. Configmaps and Secrets - 2

seul chan 2020. 6. 18. 19:25

7.3. Setting environment variables for a container

Kubernetes에서는 각각의 컨테이너에 환경변수를 지정해 줄 수 있다. 다만 아직 pod레벨에서 지정하는 방법은 없다.

환경변수로 위에서 만든 fortune image의 interval을 설정해보자. fortune-env/fortuneloop.sh

#!/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

위에서 바뀐건 INTERVAL=$1 라인을 제거한 것 뿐이다.

Specifiying environment variables in a container definition

새로운 이미지를 빌드하고 푸시한 이후 fortune-pod-env.yaml을 만들어보자.

저자가 이미 올린 luksa/fortune:env를 사용해도 된다.

apiVersion: v1
kind: Pod
metadata:
  name: fortune2s
spec:
  containers:
    - image: luksa/fortune:args
      env:
      - name: INTERVAL
        value: "30"
...

arugs: ["2"] 줄을 제거하고 env: spec을 추가해주었다.

Referring other environment variables in a variable's value

위의 예시에서는 고정된 값을 환경변수로 사용하였지만, 이 또한 다른 변수를 사용할 수 있다. $(VAR) 사용가능

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

이 경우 SECOND_VAR의 값은 foobar이 된다.

Understanding the drawback of hardcoding environment variables

이렇게 환경변수를 사용하여 하드코드 되게 되면 production이나 development같은 환경에 따라 pod 정의가 바뀌어야 한다. 이를 위해서 아래에서 ConfigMap에 대한 자세한 설명

7.4. Decoupling configuration with a configmap

introducing ConfigMaps

Kubernetes는 ConfigMap이라는 객체를 사용하여 설정 옵션들을 별개로 나눌 수 있다.

application은 ConfigMap에 직접 접근하여 읽을 필요가 없고 존재하는지도 알 필요가 없다. map의 내용들은 환경변수나 볼륨의 파일로 컨테이너에 전달된다.

app이 어떻게 ConfigMap을 사용하는 것과 상관 없이 설정을 별개의 object로 관리하는 것은 다른 환경들(dev, QA, production 등)에 대해 별개의 ConfigMaps을 사용할 수 있게 해준다.

Creating a ConfigMap

어떻게 ConfigMap을 pod에서 사용할 수 있는지 살펴보자. 간단하게 이전에 사용한 INTERVAL 환경변수를 설정에 넣고 kubectl create configmap 명령어를 사용하셔 생성 가능하다.

$ kubectl create configmap fortune-config --from-literal=sleep-interval=25
configmap "fortune-config" created

$ kubectl get configmap
NAME                   DATA   AGE
fortune-config         1      11s

$ kubectl describe configmap fortune-config
Name:         fortune-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
sleep-interval:
----
25
Events:  <none>

보통 configmap은 한 개 이상을 담는다. --from-literal을 여러개 사용해서 여러개를 추가할 수 있다.

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

kubectl get 명령어를 사용해서 만든 configmap을 YAML 형태로 확인 가능하다.

$ kubectl get configmap fortune-config -o yaml

apiVersion: v1
data:
  sleep-interval: "25"
kind: ConfigMap
metadata:
  creationTimestamp: "2020-06-09T16:06:54Z"
  name: fortune-config
  namespace: default
  resourceVersion: "4181096"
  selfLink: /api/v1/namespaces/default/configmaps/fortune-config
  uid: 9e1b8166-9a1f-4c43-8e06-65d856f9cb0b

특별한 것은 없다. 이와 반대로 위 YAML을 통해서 만들수도 있다. (metadata 섹션에 있는 것은 이름 빼고는 적을 필요는 없다.)

fortune-config.yaml

apiVersion: v1
data:
  sleep-interval: "25"
kind: ConfigMap
metadata:
  name: fortune-config-from-file
$ kubectl create -f fortune-config.yaml

kubectl create configmap 명령어로 완성된 config 파일로도 ConfigMap을 만들수 있다.

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

각각의 파일을 하나씩 생성하는 방법 말고 해당 디렉토리의 파일들을 한 번에 생성시킬 수 있따.

$ kubectl create configmap my-config --from-file=/path/to/dir

위에서 얘기한 모든 조건들을 함계 만들 수도 있다.

$ kubectl create configmap my-config
    --from-file=foo.json
    --from-file=bar=foobar.conf
    --from-file=config-opts/
    --from-literal=some=thing

image

Passing a configmap entry to a container as an environment variables

그럼 실제로 pod의 컨테이너에서는 값을 어떻게 가져올까? 세 가지 방법이 있는데 가장 심플한 방법인 "환경변수"를 사용해보자.
valueFrom 필드를 사용해서 container에서 환경 변수로 사용이 가능하다.

fortune-pod-env-configmap.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-env-from-configmap
spec:
  containers:
  - image: luksa/fortune:env
    env:                             1
    - name: INTERVAL                 1
      valueFrom:                     2
        configMapKeyRef:             2
          name: fortune-config       3
          key: sleep-interval        4
...
    1. INTERVAL이라는 이름의 환경변수를 세팅
    1. 고정값 대신 ConfigMapKey를 사용하여 init
    1. ConfigMap의 이름
    1. ConfigMap에서 사용할 변수의 키

ConfigMap을 optional로 사용할 수 있다. configMapKeyRef.optional: true를 세팅하면 된다. 이 경우 ConfigMap이 없어도 컨테이너가 실행된다.

Passing all entries of a ConfigMap as environment variables at once

ConfigMap이 여러 entry들을 가질 경우 복잡하고 에러가 날 가능성이 높다. 이를 해결하기 위해 Kubernetes 1.6 qjwjsdptjsms 모든 ConfigMap의 entry들을 환경변수로 사용할 수 있게 해 준다.

spec:
  containers:
  - image: some-image
    envFrom:                      1
    - prefix: CONFIG_             2
      configMapRef:               3
        name: my-config-map       3
...
    1. env 대신 envFrom 사용
    1. 모든 환경변수들은 CONFIG_ prefix됨
    1. my-config-map 이름의 ConfigMap 사용

prefix는 optional이기 때문에 사용하지 않으면 원래 이름을 사용한다.

Passing a ConfigMap entry as a command-line argument

이제 ConfigMap의 변수가 컨테이너에서 돌고 있는 메인 process에 어떻게 전달되는지 살펴보자.
pod.spec.container.args 필드로 바로 ConfigMap의 엔트리들을 사용할 수는 없지만, 이들을 최초 init될 때 환경변수로 주입할 수 있다.

fortune-pod-args-configmap.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-args-from-configmap
spec:
  containers:
  - image: luksa/fortune:args          1
    env:                               2
    - name: INTERVAL                   2
      valueFrom:                       2
        configMapKeyRef:               2
          name: fortune-config         2
          key: sleep-interval          2
    args: ["$(INTERVAL)"]              3
...

이전에 사용한 것과 동일하게 환경 변수를 사용하지만, $(ENV_VARIABLE_NAME) 문법을 사용하여 변수의 값을 argument로 사용할 수 있다.

Using a configMap volume to expose ConfigMap entries as files

환경 변수나 command line 인자를 사용하는 것은 보통 짧은 변수값에 사용된다.
ConfigMap은 전체 config file을 사용한다. 이를 컨테이너에 노출시킬 때 이전 장에서 언급했던 configMap volume을 사용할 수 있다.

configMap volume은 ConfigMap 파일의 각 entry를 노출시킨다.

위의 fortuneloop.sh를 수정하는 대신 조금 다른 예시를 사용해보자. fortune pod 안의 Nginx 웹서버를 설정하는 설정 파일을 사용할 것이다.

my-nginx-config.conf

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

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

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

이제 kubectl delete configmap fortune-config를 사용하여 기존에 있던 configMap을 제거하고 새로 만들어보자.

configmap-files 디렉토리를 새로 만들고 nginx confg 파일을 추가해보자. (configmap-files/my-nginx-config.conf)
이전과 같이 ConfigMapsleep-interval entry를 추가하기 위해 sleep-interval이라는 파일을 만들고 25를 추가해보자

image

그리고 디렉토리의 파일을 ConfigMap으로 만들자

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

해당 configMap의 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

nginx config의 내용과 sleep-interval이 모두 들어있는 것을 볼 수 있다.

이제 해당 변수를 volume으로 사용해보자.

Nginx는 etc/nginx/nginx.conf 파일을 읽는다.
하지만 기본 파일을 override 하지 않기 위해서 기본 파일이 포함시키는 etc/nginx/conf.d/ 디렉토리에 해당 설정을 넣을 것이다.

fortune-pod-configmap-volume.yaml

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      1
      readOnly: true
    ...
  volumes:
  ...
  - name: config
    configMap:                          2
      name: fortune-config              2
  ...
    1. configMap의 volume을 해당 경로로 설정
    1. volume은 fortune-config ConfigMap 사용
$ kubectl apply -f fortune-pod-configmap-volume.yaml
pod/fortune-configmap-volume created

$ kubectl port-forward fortune-configmap-volume 8080:80 &
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

$ curl -H "Accept-Encoding: gzip" -I localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.11.1
Date: Thu, 18 Aug 2016 11:52:57 GMT
Content-Type: text/html
Last-Modified: Thu, 18 Aug 2016 11:52:55 GMT
Connection: keep-alive
ETag: W/"57b5a197-37"
Content-Encoding: gzip     

gzip으로 정상적인 요청이 가는 것을 볼 수 있다.

실제로 /etc/nginx/conf.d/ 디렉토리에 잘 들어있는지 살펴보자.

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

sleep-interval은 실제로 nginx가 사용하지 않음에도 불구하고 해당 폴더에 같이 들어있는 것을 볼 수 있다.
이를 두 개의 ConfigMap으로 나누면 문제가 해결되지만 한 pod에 사용할 ConfigMap을 두개로 구성하는 것도 좋아 보이지는 않는다.

다행히도 configMap volume에서 필요한 것만 따로 사용이 가능하다. (여기서는 my-nginx-config.conf)

fortune-pod-configmap-volume-with-items.yaml

...
  volumes:
  - name: config
    configMap:
      name: fortune-config
      items:                             1
      - key: my-nginx-config.conf        2
        path: gzip.conf                  3
...
    1. 어떤 엔트리만 volume에 포함할건지 리스팅
    1. 해당 key에 포함된 엔트리만 사용
    1. entry의 값은 해당 경로의 파일에 저장

기존 /etc/nginx/conf.d 디렉토리에 있던 항목들은 숨겨진다는 것을 알 수 있다.
fliesystem에 volume이 마운트 되면 기존에 있던 해당 파일들은 더 이상 접근할 수 없다.

지금은 큰 사이드 이펙트가 없지만, /etc 디렉토리와 같은 많은 중요한 파일들이 있는 경우에는 문제가 심각할 수 있다.

따라서 이제 ConfigMap에서 이미 존재하는 디렉토리의 파일들은 hidden 시키지 않고 개별 파일만 추가하는 방법이 궁금할 것이다.

volumeMountsubPath 프로퍼티를 추가함으로써 전체 volume을 mount 하는 것이 아니라 특정 경로나 파일만 mount 시킬 수 있다.

spec:
  containers:
  - image: some/image
    volumeMounts:
    - name: myvolume
      mountPath: /etc/someconfig.conf      1
      subPath: myconfig.conf               2
    1. 디렉토리가 아니라 파일을 마운팅
    1. 전체 볼륨을 마운트하지 않고 myconfig.conf만 마운팅

모든 configMap 볼륨의 permission은 기본적으로 644(-rw-r-r--)읻다. defaultMode 프로퍼티를 사용하여 이를 변경할 수 있다.

  volumes:
  - name: config
    configMap:
      name: fortune-config
      defaultMode: "6600"           1
    1. 모든 파일의 권한을 -rw-rw----로 변경

Updating an app's config withou having to restart the app

이전에 얘기했던 환경변수나 cmd 인자로 설정을 넘기는 것의 단점 (프로세스가 작동중일 때 설정을 변경할 수 없다)과는 달리,

ConfigMap을 volume을 활용하여 노출시키면 pod를 다시 만들거나 재시작 할 필요 없이 설정을 변경할 수 있다.

ConfigMap을 업데이트 하면 해당 volume을 referencing하는 모든 파일이 업데이트 된다.

업데이트 된 ConfigMap이 모든 파일에 적용되기까지에는 꽤 많은 시간이 걸리는 것에 주의하라 (대략 1분 정도)

실제로 ConfigMap을 업데이트 하려먼 kubectl edit 명령어를 사용하면 된다

$ kubectl edit configmap fortune-config

에디터가 열리면 gzip ongzip off로 변경하고 파일을 저장하자.

kubectl exec 명령어를 사용하여 실제 파일이 변경되었는지 보자.

$ kubectl exec fortune-configmap-volume -c web-server cat /etc/nginx/conf.d/my-nginx-config.conf
server {
  listen              80;
  server_name         www.kubia-example.com;

  gzip off;
  gzip_types text/plain application/xml;

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

업데이트가 된 것을 볼 수 있다. (만약 업데이트가 되지 않았다면 조금 기다리거나 다시 명령어를 실행시켜보자.)

Nginx는 리로드를 하기 전까지 아직 기존의 설정에 따라 요청을 처리하고 있을 것이다. 이를 리로드 해주자.

$ kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
2020/06/14 12:43:27 [notice] 42#42: signal process started

이제 curl 요청을 보내보면 더 이상 response가 압축되지 않는 것을 볼 수 있다. (더 이상 Content-Encoding: gzip 헤더를 포함하지 않는다)

$ curl -H "Accept-Encoding: gzip" -I localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Sun, 14 Jun 2020 12:43:47 GMT
Content-Type: text/html
Content-Length: 491
Last-Modified: Sun, 14 Jun 2020 12:43:29 GMT
Connection: keep-alive
ETag: "5ee61b71-1eb"
Accept-Ranges: bytes

Kubernetes는 어떻게 리로드 없이 파일의 변화를 감지한 것일까?
이는 symbolic links 덕분이다.

$ kubectl exec -it fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/conf.d
total 4
drwxr-xr-x    2 root     root          4096 Jun 14 12:40 ..2020_06_14_12_40_56.647424611
lrwxrwxrwx    1 root     root            31 Jun 14 12:40 ..data -> ..2020_06_14_12_40_56.647424611
lrwxrwxrwx    1 root     root            27 Jun 12 15:06 my-nginx-config.conf -> ..data/my-nginx-config.conf
lrwxrwxrwx    1 root     root            21 Jun 12 15:06 sleep-interval -> ..data/sleep-interval

위에서 볼 수 있듯이 configMap 볼륨에 마운트 된 파일은 ..data 디렉토리의 파일을 심볼릭 링크로 가리킨다. ..data 디렉토리는 또한 ..2020_06_14_xxxx 디렉토리를 가리킨다.

ConfigMap이 업데이트 되면 Kubernetes는 이에 맞는 새로운 디렉토리를 다시 생성하고 이를 ..data symbolic link에 re-link 하여 한번에 모든 파일을 효과적으로 변경시킨다.

Understanding that files mounted into existing directories don’t get updated

ConfigMap-backed volume을 업데이트 할 때 주의해야할 점이 있다. 컨테이너의 전체 볼륨이 아니라 단일 파일을 마운팅한 경우에는 해당 파일은 업데이트 되지 않는다.