애플리케이션은 대부분 설정값을 가진다. 로그 레벨처럼 단순한 키-값 부터 nginx.conf처럼 완결된 설정 파일 하나까지 그 형태는 다양하다.
이런 설정을 애플리케이션에 전달하는 가장 확실한 방법은 도커 이미지 안에 설정값이나 설정 파일을 정적으로 넣어 두고 함께 빌드하는 것이다.
그러나 도커 이미지는 한번 빌드되면 불변(immutable)이다. 이미지 안에 박아 둔 설정값은 상황에 따라 유연하게 바꿀 수 없고, 값 하나를 바꾸려 해도 이미지를 다시 빌드해야 한다. 그래서 흔히 쓰는 대안이 설정값을 파드 YAML에 직접 적어 넣는 것이다.
spec:
containers:
- name: my-webserver
image: nginx:1.10
env:
- name: LOG_LEVEL
value: INFO
이 방식도 동작은 하지만, 환경 변수 값만 다르고 나머지는 동일한 YAML이 여러 개 생기는 문제가 있다.
예를 들어 운영 환경과 개발 환경에서 각각 디플로이먼트를 만들어야 한다면, 로그 레벨 같은 값만 다른 두 개의 YAML을 따로 관리해야 한다. 설정값이 YAML 본문에 섞여 있기 때문에 값 하나가 달라질 때마다 파일 전체가 갈라진다.
쿠버네티스는 이 문제를 YAML 본문과 설정값을 분리하는 방식으로 해결한다. 이를 위한 오브젝트가 컨피그맵(ConfigMap)과 시크릿(Secret)이다. 컨피그맵에는 일반적인 설정값을, 시크릿에는 비밀번호나 인증 토큰처럼 노출되어서는 안 되는 값을 저장한다.
파드 YAML은 값을 직접 갖지 않고, 컨피그맵에 담긴 값을 참조하기만 한다.
spec:
containers:
- name: my-webserver
image: nginx:1.10
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: log-level-configmap
key: LOG_LEVEL
이렇게 하면 파드 YAML은 한 개만 두고, 환경에 따라 다른 컨피그맵을 만들어 붙이는 방식으로 운영할 수 있다.
개발 환경에는 LOG_LEVEL=DEBUG를 담은 컨피그맵을, 운영 환경에는 LOG_LEVEL=INFO를 담은 컨피그맵을 두고, 같은 파드 YAML이 각 환경의 컨피그맵을 참조하게 하면 된다.
설정값과 환경 변수까지 쿠버네티스 오브젝트로 관리되므로, 설정 자체도 YAML로 정의해 파드와 함께 배포할 수 있다.
도커 스웜 모드의 config / secret
설정값을 이미지 밖에서 별도 오브젝트로 관리한다는 발상은 도커 스웜 모드의config,secret개념과 거의 동일하다. 다만, 컨피그맵은 파드가 이를 환경 변수와 볼륨 두 방식으로 받을 수 있다는 점에서 더 유연하다.
컨피그맵은 일반적인 설정값을 담는 쿠버네티스 오브젝트다. 네임스페이스에 속하므로, 컨피그맵은 네임스페이스별로 따로 존재한다. 같은 이름의 컨피그맵이라도 네임스페이스가 다르면 별개의 오브젝트다.
명령 한 줄로 만들 수 있다. 단순 문자열 값은 --from-literal 옵션으로 키-값을 지정한다.
kubectl create configmap log-level-configmap --from-literal=LOG_LEVEL=DEBUG--from-literal을 여러 번 쓰면 키-값을 여러 개 담을 수 있다.
kubectl create configmap start-k8s \
--from-literal=k8s=kubernetes \
--from-literal=container=docker
컨피그맵도 다른 리소스처럼 get으로 목록을 조회할 수 있다. configmap은 축약형 cm으로도 쓴다.
kubectl get configmap
kubectl get cm
컨피그맵에 담긴 실제 값은 -o yaml로 전체 정의를 출력하거나, describe로 확인한다.
kubectl get configmap log-level-configmap -o yaml
kubectl describe configmap log-level-configmap
생성한 컨피그맵을 파드에서 쓰려면, 디플로이먼트 등의 YAML에서 파드 템플릿에 컨피그맵을 참조하도록 정의한다. 사용 방식은 크게 두 가지이며, 첫 번째는 컨피그맵의 값을 컨테이너의 환경 변수로 넣는 것이다.
애플리케이션이 시스템 환경 변수에서 설정값을 읽어 들인다면 이 방식이 맞다. 컨피그맵의 모든 키-값을 한꺼번에 환경 변수로 가져오려면 envFrom과 configMapRef를 쓴다.
apiVersion: v1
kind: Pod
metadata:
name: container-env-example
spec:
containers:
- name: my-container
image: busybox
args: ['tail', '-f', '/dev/null']
envFrom:
- configMapRef:
name: log-level-configmap
적용 후 컨테이너 안에서 env 명령으로 환경 변수를 확인하면, 컨피그맵의 키-값이 그대로 들어와 있는 것을 볼 수 있다.
kubectl apply -f all-env-from-configmap.yaml
kubectl exec container-env-example -- env
출력에는 컨피그맵에서 온 값 외에 KUBERNETES_로 시작하는 환경 변수도 볼 수 있는데, 이는 default 네임스페이스에 존재하는 kubernetes 서비스에 대한 접근 정보로, 쿠버네티스가 서비스 정보를 컨테이너 환경 변수로 자동 주입하기 때문에 생기는 것일 뿐 컨피그맵과는 무관하다.
컨피그맵에 여러 키-값이 들어 있을 때, 그 중 특정 값 하나만 골라 환경 변수로 넣고 싶다면 valueFrom과 configMapKeyRef를 사용한다.
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: log-level-configmap
key: LOG_LEVEL
envFrom이 컨피그맵을 전체로 가져온다면, configMapKeyRef는 key로 지정한 항목 하나만 선택해 가져온다는 점이 다르다.
두 번째 방식은 컨피그맵의 값을 파드 내부의 파일로 마운트하는 것이다. 애플리케이션이 nginx.conf 같은 설정 파일에서 값을 읽어 들인다면 이 방식이 맞다.
파일 마운트에는 두 항목이 필요하다. spec.volumes에 사용할 볼륨의 목록을 정의하고, spec.containers.volumeMounts에서 그 볼륨을 컨테이너 내부의 어느 경로에 마운트할지 지정한다.
apiVersion: v1
kind: Pod
metadata:
name: configmap-volume-pod
spec:
containers:
- name: my-container
image: busybox
args: ['tail', '-f', '/dev/null']
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: log-level-configmap
적용한 뒤 마운트 경로를 확인하면, 컨피그맵의 키 이름이 그대로 파일 이름이 되어 있다. 위 예에서 LOG_LEVEL 키는 /etc/config/LOG_LEVEL 파일이 되고, 그 파일 안에 값이 저장된다.
kubectl apply -f volume-mount-configmap.yaml
kubectl exec configmap-volume-pod -- ls /etc/config
컨피그맵에 여러 키가 있을 때 그 중 일부만 파일로 가져오려면 items를 쓴다. items는 컨피그맵에서 가져올 키의 목록이고, path는 마운트 경로에 놓일 파일의 이름이다.
키 이름과 다른 파일 이름을 쓰고 싶을 때도 path로 지정한다.
volumes:
- name: config-volume
configMap:
name: log-level-configmap
items:
- key: LOG_LEVEL
path: log_level
이 방식은 설정 파일 그 자체를 컨피그맵으로 다룰 때 주로 사용한다. nginx.conf, mysql.conf 같은 파일 전체를 컨피그맵에 담아 두고, 파드 내부의 설정 경로에 그대로 마운트하는 식이다.
--from-literal과 달리 설정 파일 전체를 컨피그맵으로 만들 때는 --from-file 옵션을 쓴다.
echo "Hello, World!" >> index.html
kubectl create configmap index-file --from-file index.html
별도로 키를 지정하지 않으면 파일 이름이 키가 되고, 파일 내용이 값이 된다. 위 예에서는 index.html이 키, 파일 내용이 값으로 저장된다. --from-file을 여러 번 쓰면 여러 파일을 한 컨피그맵에 담을 수 있고, --from-file=<키>=<파일> 형식으로 키 이름을 직접 지정할 수도 있다.
kubectl describe configmap index-file한변 key=value 형식의 줄이 여러 개 담긴 설정 파일을 통째로 컨피그맵으로 옮기고 싶다면 --from-env-file을 쓴다. 이 옵션은 파일의 각 줄을 하나의 키-값으로 해석해 컨피그맵에 넣는다. --from-file이 파일 하나를 통째로 값으로 담는 것과 달리, --from-env-file은 파일 안의 줄들을 각각의 키-값으로 풀어낸다.
kubectl create configmap from-envfile --from-env-file multiple-keyvalue.env
kubectl get cm from-envfile -o yaml
컨피그맵도 다른 리소스처럼 YAML로 정의해 관리하는 편이 낫다. 매번 명령을 치는 대신, --dry-run=client -o yaml로 컨피그맵의 YAML을 생성해 파일로 저장한 뒤 apply한다.
kubectl create configmap my-configmap \
--from-literal=mykey=myvalue \
--dry-run=client -o yaml > my-configmap.yaml
kubectl apply -f my-configmap.yaml
여기서 dry run은 작업을 실제로 반영하지 않고 실행 가능 여부만 검토하거나 결과물만 확인하는 방식을 뜻한다. --dry-run=client는 클러스터에 아무것도 만들지 않고, 만들어질 YAML만 출력한다.
이 출력을 파일로 저장하면 손으로 처음부터 작성하지 않아도 올바른 형식의 컨피그맵 정의를 얻는다. 또는 키-값 데이터가 지나치게 많아지는 경우 kubectl 1.14 버전부터 내장된 kustomize 기능으로 컨피그맵을 생성하는 방식도 고려할 수 있다.
