kubernetes는 각 pod의 container마다 custom 환경변수를 설정할 수 있도록 해준다.
Note: container를 실행할 때
ENTRYPOINT
등의 설정을 kubernetes에서command
,args
라는 keyword로 오버라이드가 가능하다. 마찬가지로 container의 환경변수 역시도 kubernetes pod 생성 시 오버라이드 할 수 있다.
다음의 그림과 같이 pod내부의 각 container마다 다른 환경변수를 설정해줄 수 있다.
다음으로 이전에 만들었던 fourtune
script를 환경변수에 따라 동작을 달리 하도록 변경해보도록 하자.
#!/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
위의 script는 INTERVAL
환경변수를 추가한 것이다. 이 환경변수 값만큼 fortune script는 기다렸다가 fortune명령어를 수행한다. 위의 script는 luksa/fortune:env
라는 이미지로 dockerhub에 올려져 있다.
다음으로 환경변수를 설정하는 방법들에 대해서 알아보자.
luksa/fortune:env
image를 사용해서 pod를 만들어보도록 하자. 이 때 environment를 다음과 같이 직접 설정할 수 있다.
kind: Pod
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
value: "30"
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
env
를 통해서 원하는 환경변수를 key-value형식으로 넘겨주면 된다. 주의할 것은 해당 환경변수는 pod 레벨이 아니라 container 레벨이라는 것이다. 따라서 같은 환경변수를 pod에서 서로 다른 각기의 container마다 다를 값으로 구성할 수 있다.
추가적으로 환경변수 값을 다른 환경변수 값을 이용해 쓸 수 있다. $(VAR)
을 통해서 다른 환경변수 값을 레퍼런스 할 수 있다는 것이다. 아래의 예시를 보자.
env:
- name: FIRST_VAR
value: "foo"
- name: SECOND_VAR
value: "$(FIRST_VAR)bar"
SECOND_VAR
는 FIRST_VAR
의 값을 사용하기 때문에, SECOND_VAR
의 값은 foobar
이 되는 것이다.
그러나 이렇게 환경변수를 하드코딩하는 것은 좋은 방법이 아니다. 왜냐하면 환경변수를 pod description에 하드코딩하게 되면 pod와 환경변수 간의 강한 의존성이 생기기 때문에, 각 환경변수에 따른 pod 정의를 만들 수 밖에 없다. 가령, production환경의 환경변수와 test환경의 환경변수를 달리 해야하는데, 이 때 환경변수를 pod description에 하드코딩 한다면, 환경변수만 다르고 나머지는 동일한 두개의 pod description을 만들어야 하는 것이다.
kubernetes는 이를 위해서 pod와 환경변수 간의 decoupling으로 configmap을 만들었다.
kubernetes는 configuration 옵션으로 pod와 분리된 kuberentes object인 ConfigMap
을 제시한다. 이는 key-value로 된 환경변수 리스트로 literal하게 만들 수도 있고, file로도 만들 수 있다.
application 코드는 configMap의 존재를 알 필요가 없다. kubernetes상에서 configMap에 있는 환경변수 key-value를 지정된 container로 주입해주기 때문이다. application은 오직 환경변수에 접근만 하면 된다.
위의 그림과 같이 pod는 그저 configMap volume을 통해서 configMap을 가져오고 이를 환경변수로 로딩해주는 것이다.
또는 configMap을 통해서 config file을 특정 지점에 전달해주는 것 또한 가능하다. 이를 통해서 application은 지정된 file path만을 보고있다가 configMap에 의해서 지정된 file path에 config file이 생기면 이를 읽고 configuration을 설정하는 것이 가능하다.
configMap을 사용하는 가장 큰 강점 중 하나는 configMap 이름은 동일하지만 가지고 있는 value는 다르게 할 수 있어, pod는 configMap의 이름만 가지고 application 설정이 가능하다는 것이다. 즉, configMap이름이 app-config
인데 development환경과 production환경에서 다르게 할 수 있다. 때문에 configMap만 다르게 하고 pod description은 변경하지 않을 수 있다.
다음으로 ConfigMap
을 만들어보도록 하자.
configMap의 entry들은 libral하게 만들 수도 있고, 내장된 file을 이용해 이래르 읽도록 하여 구성할 수도 있다.
다음은 command를 통해서 literal하게 환경변수를 구성하는 방법이다.
kubectl create configmap fortune-config --from-literal=sleep-interval=25
다음의 configMap은 sleep-interval=25
라는 환경변수 entry를 만든다.
아래와 같이 한번에 여러 개의 환경변수도 만들 수 있다.
kubectl create configmap myconfigmap --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
이제 우리가 만든 fortune-config
configMap에 대해서 알아보도록 하자.
kubectl get configmaps fortune-config -o yaml
apiVersion: v1
data:
sleep-interval: "25"
kind: ConfigMap
metadata:
creationTimestamp: "2023-12-03T11:12:27Z"
name: fortune-config
namespace: default
resourceVersion: "21269"
uid: c7095569-6dbb-46a1-b3fb-2ca878fd21dc
data
부분이 환경변수 값 구성 부분이다. 별로 특별할 것이 없음을 알 수 있다.
이전에 했듯이 yaml파일이 있다면 create -f
옵션으로도 호출이 가능하다.
kubectl create -f fortune-config.yaml
다음으로 환경변수로 file을 지정할 수도 있는데, kubernetes에서 파일을 읽은 뒤 파일 이름을 key로 하여 읽어 들인 file내용을 value로 쌍을 맞춘다. 이 경우 --from-file
을 사용하고 --from-literal
과 같이 사용할 수 있다.
다음과 같이 사용할 수 있다.
kubectl create configmap my-config
--from-file=foo.json
--from-file=bar=foobar.conf
--from-file=config-opts/
--from-literal=some=thing
foo.json
을 읽어들여서 foo.json
이 key이고 그 내용이 value가 된다. 즉, key를 따로 지정하지 않으면 file이름이 key가 된다.foobar.conf
을 읽어들여서 bar
이 key가 되고 value는 foobar.conf
가 된다.config-opts/
directory를 읽어들여서 내부의 file들을 모두 읽는다. 이 경우는 file마다 key를 지정할 수 없어 file이름이 key가 되고 value는 파일의 내용이 된다.some=thing
은 file을 읽어들인 것이 아니라 literal하게 환경변수를 설정한 것이다.이를 도식화하면 다음과 같다.
이제 ConfigMap
을 pod에 적용시켜보도록 하자.
먼저 pod에 ConfigMap
의 환경변수를 적용시키는 방법 중 하나는 valueFrom
을 사용하는 것이다.
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
...
먼저, containers
의 luksa/fortune:env
에서 env
를 설정하도록 한다. env
는 INTERVAL
이고 이 값은 valueFrom
을 통해서 외부로 가져오는데, 외부는 configMapKeyRef
으로 우리가 만든 configMap을 지정할 수 있다. fortune-config
configMap을 지정하고 key는 sleep-interval
로 지정하도록 한다. 이렇게 하면 INTERVAL
의 값을 sleep-interval
의 값으로 채울 수 있는 것이다.
다음과 같이 정리할 수 있다.
pod가 시작될 때 container가 referencing하고 있는 configMap이 없다면 어떻게 될까? 이 경우 pod내 다른 container들은 문제없이 구동되고, 존재하지 않는 configMap을 지정하고 있는 container는 생성에 실패한다. 이 후에 해당 configMap이 만들어지게 되면 해당 container 또한 fail상태에서 running으로 변경된다.
만약 원한다면 configMap-KeyRef.optional: true
라는 설정을 통해서 configMap이 없어도 pod의 container를 실행시킬 수 있다.
위의 예제는 configMap에 있는 하나의 entry를 container에 넣어주는 방법이었다. 이제 configMap에 있는 모든 entry들을 한번에 넣어주는 방법을 확인해보도록 하자.
ConfigMap
이 3개의 key인 FOO
, BAR
, FOO-BAR
로 이루어져 있다고 하자. 이 ConfigMap
의 모든 entry들을 pod에 expose할 수 있는데 envFrom
attribute를 사용하여 가능하다.
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_
configMapRef:
name: my-config-map
env
대신에 envFrom
을 사용하며, configMapRef
를 통해서 ConfigMap
을 레퍼런싱 할 수 있다. 이때 my-config-map
에 존재하는 환경변수들은 CONFIG_
라는 접두사를 갖게 된다. 즉 CONFIG_FOO
, CONFIG_BAR
과 같이 container에 환경변수로 제공되는 것이다. 참고로 prefix
는 optional이다.
만약 다음과 같이 ConfigMap
을 만들고 some-image
에게 환경변수를 넘겨주었다면 아마 CONFIG_FOO
, CONFIG_BAR
는 제공되지만 CONFIG_FOO-BAR
는 제공되지 않을 것이다. 이는 환경변수의 format이 아닌 -
를 사용하였기 때문이다. 즉, kubernetes상에서는 환경변수의 format에 어긋나는 경우 이를 환경변수로 import하지 않는다는 것이다.
ConfigMap
의 환경변수를 이용해서 pod의 arguments로 넘겨줄 수 있다. 가령, ConfigMap
에 있는 sleep-interval
값을 읽어서, luksa/fortune:args
container에서 사용하는 INTERVAL
arguments에 맵핑시킬 수 있다.
다음과 같이 yaml파일을 구성할 수 있다.
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)"]
luksa/fortune:args
는 어떠한 환경변수도 사용하지 않는다. 다만 argument로 입력을 받는데 이 입력이 INTERVAL
인 것이다. 그리고 이 값은 configMapKeyRef
를 통해서 fortune-config
ConfigMap의 sleep-interval
값을 사용한다.
즉, args
로 전달되는 값은 fortune-config
ConfigMap의 sleep-interval
이다. 그리고 이 값을 통해서 luksa/fortune:args
를 실행할 때 맨 처음에 사용하는 것이다.
ConfigMap
을 이용해서 환경변수 entry들을 하나하나 설정하는 방법은 정말 적은 환경변수들을 이용할 때만 사용하고 보통은 file전체를 전달하여 configuration을 설정할 수 있도록 한다. 이때 사용되는 특별한 volume이 바로 configMap
volume이다. configMap
volume은 파일로 ConfigMap
의 각 entry를 노출시켜 container에서 동작 중인 process가 file을 읽음으로서 entry value를 얻도록 한다.
즉, config file을 container에 심어서 container에서 구종중인 process가 이를 읽게하는 방법이다.
예제로 nginx web-server의 configuration파일을 전달하여 client에게 응답을 전송할 때, gzip으로 응답을 압축하도록 하자. 다음의 nginx configuration파일이 필요하다.
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;
}
}
gzip on
부분이 plain text와 XML파일들을 압축할 수 있도록 하는 부분이다.
일단 이전에 만들었던 ConfigMap
을 삭제하도록 하자.
kubectl delete configmaps fortune-config
configmap "fortune-config" deleted
다음으로 ConfigMap
을 만들되 위의 my-nginx-config.conf
파일을 포함하고, fortune
에서 사용할 sleep-interval
환경변수 entry도 포함하도록 하자.
다음과 같이 configmap-files
라는 directory를 만들어놓고 my-nginx-config.conf
와 sleep-interval
파일을 각각 만들면 된다.
ls -al configamp-files/
...
-rw-rw-r-- 1 gyu gyu 221 12월 4 21:36 my-nginx-config.conf
-rw-rw-r-- 1 gyu gyu 2 12월 4 21:37 sleep-interval
그리고 configMap을 만들 때 해당 directory를 통째로 configMap에 넘겨주면 된다. kubernetes api에서 해당 directory의 파일들을 읽어 configMap에 처리하게 된다. key는 file이름이고 file내용이 값이다.
kubectl create configmap fortune-config --from-file=./configamp-files/
configmap/fortune-config created
다음으로 배포된 configMap이 어떤 모습을 가지는 지 확인하도록 하자.
kubectl get configmaps 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
metadata:
...
data
로 두 부분인 my-nginx-config.conf
와 sleep-interval
이 있는 것을 알 수 있다. my-nginx-config.conf
는 config파일이 통째로 들어가 있고, sleep-interval
은 단일 value값만 들어간 것으로 알 수 있다.
이제, volume을 사용해서 configMap파일을 pod의 container에 적재하도록 하자. 우리는 이전에 volume을 사용하는 방법을 배웠다. 따라서 특별한 것은 없고 configMap의 파일을 volume에 어떻게 적재하는 지만 알아내면 volume을 통해서 pod에 configMap 파일을 적재하는 것이 가능하다.
먼저 Nginx의 config파일은 /etc/nginx/nginx.conf
에 default로 저장된다. 단, 이 부분은 default configuration파일이므로 /etc/nginx/conf.d/
에 config파일을 넣도록 하자. 해당 디렉토리에 넣으면 nginx에서 config file을 읽어서 모두 적용시킨다.
이제 우리가 원하는 모양은 다음과 같다.
이제 pod를 만들어서 volume을 적재해 configMap의 config파일을 넣어주도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
name: html-generator
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: config
mountPath: /tmp/whole-fortune-config-volume
readOnly: true
ports:
- containerPort: 80
name: http
protocol: TCP
volumes:
- name: html
emptyDir: {}
- name: config
configMap:
name: fortune-config
다음은 두 개의 container로 이루어져 있는데 luksa/fortune:env
는 valueFrom
을 통해서 INTERVAL
값을 설정한다. 다음으로 nginx:alpine
은 volumeMounts
를 통해서 파일을 마운팅하는데 name
이 config
이고 mountPath
는 /etc/nginx/conf.d
라는 것을 알 수 있다.
config
는 아래의 volumes
부분의 config
와 일치하고 volume으로 configMap
을 링킹하고 있는 것을 알 수 있다. config
는 fortune-config
confgMap을 레퍼런스하기 때문에 이 안에 있는 모든 config file들을 가져와 /etc/nginx/conf.d
디렉터리에 로딩한다.
그렇다면 nginx configuration이 정말 잘되었는 지 확인해보도록 하자. 먼저 nginx가 동작중인 pod port를 외부에 forwarding시켜준 뒤에 요청을 보내보도록 하자. 우리가 만든 configuration이 적용되었다면 응답으로 압축된 응답이 전달될 것이다.
kubectl port-forward fortune-configmap-volume 8080:80 &
[1] 55685
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
다른 shell을 열어 요청을 보내보도록 하자.
curl -H "Accept-Encoding: gzip" -I localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Mon, 04 Dec 2023 13:18:42 GMT
Content-Type: text/html
Last-Modified: Mon, 04 Dec 2023 13:18:22 GMT
Connection: keep-alive
ETag: W/"656dd19e-34"
Content-Encoding: gzip
Content-Encoding: gzip
에서 볼 수 있듯이 응답이 gzip
으로 압축된 것을 볼 수 있다.
성공할 확인하였지만, 실제로 /etc/nginx/conf.d
디렉터리에 configuration파일이 적재되어있는 지 확인해보도록 하자.
kubectl exec fortune-configmap-volume -c web-server -- ls /etc/nginx/conf.d
my-nginx-config.conf
sleep-interval
sleep-interval
은 nginx
에서 사용하지 않지만 fortune-config
configMap을 volume으로 마운팅할 때 같이 온 것을 알 수 있다. 필요에 따라 configMap을 여러 개를 두어서 필요한 부분에만 넣어줄 수 있지만, 사실 하나의 pod에 쓰이는 환경변수들이기 때문에 굳이 번잡하게 나눌 필요는 없다.
다행스럽게도 configMap
volume에서 ConfigMap
의 entry를 뽑아올 수 있는 로직이 존재한다. volume의 items
attribute를 사용하면 된다.
...
volumes:
- name: config
configMap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: gzip.conf
items
는 configMap
인 fortune-config
에서 일부 entry를 가져오도록 하는데, items
아래에 list로 적어주면 된다. key
는 ConfigMap
에서 entry의 key이고, 해당 key의 값을 gzip.conf
파일에 저장하여 container에 제공한다는 것이다.
따라서, 위의 yaml파일이 설정되면 /etc/nginx/conf.d
디렉터리에는 gzip.conf
만 있게되고 내용을 살피면 my-nginx-config.conf
와 동일하다.
한가지 조심해야할 것은 volume을 특정 directory에 마운트 할 때 기존의 file들이 모두 삭제된다는 것이다. 즉, 위의 경우는 /etc/nginx/conf.d
directory에 config file들을 적재하므로 /etc/nginx/conf.d
안에 있던 기존의 모든 file들이 삭제된다.
위 경우는 문제없지만, 만약 /etc
나 /var
과 같이 linux 시스템 파일이 적재되는 파일 시스템 directory에 마운팅시키게 되면 모든 파일들이 삭제(숨겨짐)되므로 side effect가 생길 수 있음을 알아두도록 하자.
물론 기존 디렉터리에 있던 모든 파일들을 삭제하지 않고, 단일 file이나 디렉터리를 마운팅하는 방법도 있다.
confgMap
volume으로 myconfig.conf
file을 가지고 있다고 하자. 그리고 이것을 container
의 /etc
디렉터리에 someconfig.conf
로 추가하자고 하고 싶다고 하자. 단, myconfig.conf
file을 /etc
디렉터리에 someconfig.conf
로 저장할 때, 기존의 /etc
파일들이 삭제(숨김처리)되지 않도록 하기 위해서 subPath
라는 것을 사용할 수 있다.
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf
subPath: myconfig.conf
mountPath
에 /etc/someconfig.conf
라고 파일로 configuration을 저장하도록 하되 해당 파일로 subPath
에 configMap
의 myconfig.conf
entry를 마운팅하도록 한다. 이렇게 subPath
를 사용하면 기존의 /etc
아래의 기존 파일들을 삭제하지 않고 파일을 추가할 수 있다.
한 가지 명심할 것은 subPath
를 사용하여 개개의 file을 설정하는 것은 file들을 업데이트하는 것과 관련해서 큰 결점을 가진다. 이에 대해서는 추후에 알아보도록 하자.
추가한 파일의 permission도 조정할 수 있는데, 기본적으로 configMap volume에서의 file들은 모두 644
(rw-r-r--)로 설정되어 있다. 원하는 경우, 이를 수정할 수 있다.
volumes:
- name: config
configMap:
name: fortune-config
defaultMode: "6600"
이렇게 만들면 fortune-config
에서의 모든 파일들은 permission이 6600
이 된다.
환경변수 또는 command-line arguments를 configuration source로 사용하는 것의 한가지 결점은 process가 진행되는 동안에, 이 값들을 변경할 수 없다는 것이다. 오직 ConfigMap
을 업데이트한 다음에 pod를 재생성하는 방법 밖에 없다.
ConfigMap
이 업데이트될 때, ConfigMap
을 레퍼런싱하는 모든 volume 안의 파일들은 업데이트된다. 파일들이 변경된 것을 감지하는 것은 온전히 process의 구현에 따라 달라지지만, kubernetes는 file이 변경된 뒤에 container에게 signal을 전달하는 것을 지원한다.
먼저, kubectl edit
명령어로 nginx의 compression옵션을 off
시키도록 하자.
kubectl edit configmap fortune-config
editor가 나오게 되고 gzip on
을 gzip 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 process는 처음에만 해당 configuration파일을 읽을 뿐, 이를 watch하고 있지 않기 때문에 반영이 안된다는 것이다.
따라서, nginx
명령어로 nginx
process에게 config reload
signal을 보내도록 하자.
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
다음과 같이 pod를 재시작하지 않고도 signal을 주어서 새로운 config가 반영된 process를 실행시킬 수 있다.
한 가지 조심해야할 것이 있는데, 위에서 말했듯이 ConfigMap
의 모든 파일들을 로딩하는 것이 아니라, subPath
로 일부의 file만 로딩하는 것은 config file을 수정하게 될 때, 수정된 내용이 반영되지 않는다는 문제가 있다.
왜냐하면 모든 ConfigMap
파일들은 symbolic links를 통해서 atomically하게 업데이트하기 때문이다.
kubectl exec -it fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/conf.d
total 4
drwxr-xr-x 2 root root 4096 Dec 4 14:36 ..2023_12_04_14_36_38.888836059
lrwxrwxrwx 1 root root 31 Dec 4 14:36 ..data -> ..2023_12_04_14_36_38.888836059
lrwxrwxrwx 1 root root 27 Dec 4 13:09 my-nginx-config.conf -> ..data/my-nginx-config.conf
lrwxrwxrwx 1 root root 21 Dec 4 13:09 sleep-interval -> ..data/sleep-interval
configMap
의 file들이 마운팅된 /etc/nginx/conf.d
디렉터리를 확인하면 my-nginx-config.conf
와 sleep-interval
이 ..data/
로 symbolic link되어있고 data
는 ..2023_12_04_14_36_38.888836059
에 link되어있는 것을 볼 수 있다.
kuberntes에서는 ConfigMap
이 업데이트되면 업데이트 날짜를 기준으로 새로운 디렉터리를 만들고 이를 data
에 link시킨다. 따라서 모든 file들이 atomic하게 한 번에 업데이트되는 것이다.
문제는, subPath
를 통해서 ConfigMap
의 개별 파일들을 마운트하게 되는 경우, 파일과 ConfigMap
사이의 symbolic link가 떨어져 나가기 때문에 ConfigMap
이 수정되어서 새로운 디렉터리를 만들어도 개별 파일들에게는 영향을 미치지 않는 것이다.
- name: config
mountPath: /etc/nginx/conf.d/my-nginx-config.conf
subPath: my-nginx-config.conf
readOnly: true
실제로 위와 같이 subPath
로 ConfigMap
의 개별 file을 할당해놓으면, 개별 file에 symbolic link가 없는 것을 확인할 수 있다.
kubectl exec -it fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/conf.d
total 8
-rw-r--r-- 1 root root 1093 Dec 4 15:06 default.conf
-rw-r--r-- 1 root root 222 Dec 4 15:06 my-nginx-config.conf
따라서 subPath
로 개별 config파일들을 마운트하는 것보다, configMap
전체를 마운트하는 것이 더 좋은 선택지이다.
자동으로 config file이 변경되는 것을 감지하는 것이 application에 달려있다라고 했는데, 이를 도와주는 library들이 있다. 대표적으로 golang의 viper
가 있는데 viper
는 환경변수나 파일의 값이 변경되면 이를 확인하여 새로운 값으로 업데이트해준다.
이렇게 application쪽에서 감지하지 않으면, 실제로 실행중인 process에 변경된 configuration을 적용할 방법이 딱히 존재하지 않는다.
일반적으로 configuration data는 보안에 민감한 data가 아니기 때문에 따로 관리할 필요는 없었지만, 일부 data는 보안에 민감한 정보를 담고 있기 때문에 외부에 노출되기 꺼려지는 것들이 있다. 가령 credential이나 private encrytion key들로 이러한 data는 secure하게 보관되어야 한다.
Secret
은 ConfigMap
과 별반 다를바 없는데, 똑같이 key-value map으로 이루어져 있고 ConfigMap
처럼 각 entry들이 환경변수로 container에 주입되거나, volume을 통해 file로 container에 노출될 수 있다.
단 몇가지 차이점이 있는데,
1. Secret
은 Secret
에 접근해야하는 pod가 있는 node에만 배포된다.
2. 또한, node자체에서 Secret
은 memory에만 저장되지 disk로 저장되지 않는다. 따라서, Secret
삭제 후에 disk에 값이 남아있지 않는다.
원래는 master node 자체(etcd
)에서 Secret
은 암호화되지 않은 형식으로 저장되었다. 이는 master node가 Secret
에 저장된 민감한 data를 안전하게 보관하기 위해서 보안화되어야 한다는 의미였다. 그러나 kubernetes version 1.7이후로 etcd는 Secret
을 암호화된 형식으로 저장하여, 시스템을 더욱 견고하게 하였다.
Secret
을 사용할 지 ConfigMap
을 사용할 지에 대해서는 다음의 경우에서 선택하면 된다.
1. 보안에 민감하지 않은 plain한 configuration data를 저장할 때는 ConfigMap
을 사용할 것
2. 보안에 민감하고, key
아래에 보관되어야 하는 data를 저장할 때는 Secret
을 사용할 것, 만약 보안에 민감한 data와 민감하지 않는 data가 함께 있는 경우 Secret
에 저장하도록 한다.
이제 Secret
을 만들어보도록 하자. 우리가 이전에 만든 ortune-configmap-volume
pod의 nginx에 https 설정을 추가하기 위해서, certificate와 private key가 필요하다.
먼저 certificate와 private key를 만들어보도록 하자.
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj "/CN=www.kubia-example.com"
위의 명령어를 치면, https.cert
와 https.key
가 만들어진다.
다음으로 Secret
이 어떻게 동작하는 지 확인하기 위해서 foo
라는 파일을 만들고 bar
이라는 데이터를 넣도록 하자.
이제 kubectl create secret
명령어를 통해서 다음 3개의 file들을 가지는 Secret
을 만들도록 하자.
kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
secret/fortune-https created
generic
는 secret의 종류인데, https를 만들기위함이니까 tls
로 만들 수도 있다. 단 이 경우 entry name이 달라지므로 generic
으로 만든 것이다.
Secret
과 ConfigMap
의 차이를 알기위해서 kubectl get
명령어를 두 kubernetes resource를 확인해보도록 하자.
kubectl get secrets fortune-https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJ...
https.key: LS0tLS1CRU...
kind: Secret
metadata:
...
다음의 Secret
을 ConfigMap
과 비교해보도록 하자.
kubectl get configmaps fortune-config -o yaml
apiVersion: v1
data:
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;
}
}
sleep-interval: "25"
kind: ConfigMap
...
두 resource의 차이는 Secret
은 각 entry
가 base64로 인코딩되어있다는 것이다. 반면 ConfigMap
은 plain text이기 때문에, 외부에 노출되기 쉽다. Secret
은 base64로 인코딩된 data를 pod에서 가져올 때 자동으로 디코딩해준다. 즉, 저장할 때는 인코딩, 가져올 때는 디코딩을 자동으로 해주는 것이다.
한가지 명심할 것은 Secret
은 언제나 sensitive data에만 쓰이는 것은 아니다. base64인코딩과 디코딩을 자동으로해주기 때문에 binary data
에도 쓰기에 안성맞춤이다.
그러나 모든 데이터가 binary
data이지 않다. 이런 모든 string데이터들은 Secret
의 stringData
라는 field에 저장된다. 다음의 경우를 보자.
kind: Secret
apiVersion: v1
stringData:
foo: plain text
data:
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCekNDQ...
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcE...
stringData
field는 write-only이다. 조심해야할 것은 read-only가 아니라는 것이다. 즉, 값을 쓰기만 하는 것이 가능하다는 것이다. stringData
field는 -o yaml
옵션으로 안보일텐데, 이는 write-only
이기 때문이다. 대신 stringData
에 쓰여진 모든 entry들은 data
로 저장된다. 따라서 stringData
에 적힌 모든 entry들이 base64인코딩되어 data
에 적히는 것이다. 위의 경우는 foo
에 적힌 bar
이 base64로 인코딩되어서 data
에 들어간다.
pod
에서 Secret
데이터를 읽을 때는 별도의 디코딩이 필요하지 않다. 자동으로 pod에 Secret
데이터 파일이나 환경변수가 디코딩되어 container에 들어가기 때문이다.
이제 nginx에서 secret 데이터를 사용해 https를 제공할 수 있도록 하자. 먼저 fortune-config
에서 nginx
관련된 configuration을 수정하기로 하자.
kubectl delete configmaps fortune-config
이전의 configmap을 지우고 다음과 같이 새로 만들도록 하자.
apiVersion: v1
kind: ConfigMap
metadata:
name: fortune-config
data:
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"
ssl설정을 완료한 다음에 배포해주도록 하자.
kubectl create -f fortune-config.yaml
configmap/fortune-config created
nginx에서는 /etc/nginx
의 certs
디렉터리에서 https.cert
, https.key
정보를 가지고 https를 구축한다. 따라서 Secret
에 있는 cert정보와 private key를 /etc/nginx/certs
디렉터리에 가져다 놓아야한다.
다음으로 Secret
을 volume에 마운팅하기 위해서 pod를 수정하도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: fortune-pod-configmap-and-secret
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
name: html-generator
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: config
mountPath: /tmp/whole-fortune-config-volume
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
- name: certs
secret:
secretName: fortune-https
달라진 부분은 web-server
container의 volumeMounts.name
에 certs
가 추가되었고 mountPath
가 /etc/nginx/certs
라는 것이다. 또한, https
port를 위해서 ports.containerPort: 443
을 열어주었다. 마지막으로 volumes.name
으로 certs
를 만들고 secret
으로 fortune-https
를 연결시켰다.
그림으로 표현하면 다음과 같다.
다시 Pod를 재생성한 다음에 nginx에서 certs
디렉터리에 있는 cert와 private key를 읽고 실제로 https를 지원하고 있는 지 알아보도록 하자.
kubectl delete -f ./fortune-pod-configmap-volume.yaml
pod "fortune-configmap-volume" deleted
kubectl create -f ./fortune-pod-configmap-and-secret.yaml
pod/fortune-configmap-volume created
다음으로 port-forward
를 해주도록 하자.
kubectl port-forward fortune-pod-configmap-and-secret 8443:443 &
curl https://localhost:8443 -k
You will be imprisoned for contributing your time and skill to a bank robbery.
성공적으로 응답을 받은 것을 확인할 수 있다.
이전에 Secret
은 in-memory filesystem(tmpfs)를 사용하여 Secret
file들을 저장한다고 하였다. 따라서 다음의 명령어를 통해서 실제 파일들이 저장되어있는 지 확인할 수 있다.
kubectl exec fortune-configmap-volume -c web-server -- mount | grep certs
tmpfs on /etc/nginx/certs type tmpfs (ro,relatime,size=16170152k,inode64)
tmpfs
가 사용되기 때문에 secret에 저장된 민감한 정보들은 disk에 쓰이는 것이 아니라는 것을 알 수 있다.
configmap에서 특정 값을 환경변수로 노출하였듯이 secret에서도 가능하다. 다음은 fortune-https
secret에 있던 foo
의 값을 container에서 FOO_SECRET
으로 접근할 수 있도록 해준다.
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo
valueFrom
에 secretKeyRef
를 썼기 때문에 Secret
으로부터 값을 얻을 수 있다는 것을 알 수 있다. 단, secret
을 쓸 정도로 민감한 데이터를 환경변수로 노출시키지 말아야한다. 환경변수는 application이 구동되면서 의도치 않게 로깅될 수 있으며, 또 다른 환경변수 설정 방식에 의해 덮여쓰일 수도 있다. 또한, 환경변수들은 모두 자식 프로세스를 만들 때 부모 프로세스의 환경변수를 가져감으로 원치 않은 보안사고가 날 수 있다.
따라서 Secret
은 volume으로 쓰는 것이 가장 좋다.
지금까지는 public image registry(docker hub, local registry)를 사용했지만, private registry를 사용하는 경우에는 certificate가 필요하다. 이는 kubernetes에서 private registry의 image를 pull하기 위해서는 인증된 certificate가 있는 경우에만 허용하도록 하기 때문이다.
이 certificate가 바로 Secret
이 담고 있는 data인 것이고, private registry는 Secret
에 인증 정보를 담도록 하여, 사용자만 만든 Secret
이 private registry를 사용할 수 있는 지 없는 지 확인한다. 만약 인증에 성공한다면 kubernetes cluster에서 private registry를 통해서 image를 가져와 pod를 만들 수 있는 것이다.
1. 사용자 --> Secret생성 --> pod생성 --> kubernetes에서 private registry에 접근
2. private registry에 대한 인증 정보가 담긴 Secret 확인 --> 인증 성공 시 pod가 성공적으로 배포
도커 허브에서 private registry를 구축할 수 있다. 또는 harbor나 os local상에 local registry를 구축하여 private하게 사용할 수 있다.
private registry에서 image를 가져와 pod를 만들기 위해서는 다음의 두 가지를 설정해야한다.
1. private registry 인증 정보를 담은 Secret
2. pod manifest의 imagePullSecrets
field안에서 Secret
을 레퍼런싱
가령, docker-hub의 private registry를 만들었다면 다음과 같이 Secret
의 만들면 된다.
kubectl create secret docker-registry mydockerhubsecret \
--docker-username=myusername --docker-password=mypassword \
--docker-email=my.email@provider.com
한 가지 주목해야할 것은 이전에 우리가 만든 secret
은 generic
type인 반면에 여기서는 docker-registry
라는 type이 쓰였다. 이는 특수 type으로 private registry에서 image를 가져올 때 사용한다.
다음으로 kubernetes에서 private registry image를 가져오기 위해서, 어떤 Secret
을 써야하는 지 알려주어야 한다. 이는 pod description의 spec.imagePullSecrets
에 적어주면 된다.
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: main
imagePullSecrets
에 우리가 만든 docker-registry Secret인 mydockerhubsecret
을 넣어주면 kubernetes에서 해당 pod를 만들 때 Secret
을 읽어서 private registry에 인증을 완료한 뒤, image를 가져온다.