설정 (ConfigMaps/Secrets)
설정 (ConfigMaps/Secrets)
쿠버네티스는 파드(Pod)를 설정하기 위해 쓸 수 있는 여러 리소스를 제공해요. 그중에서도 ConfigMap과 Secret은 애플리케이션 코드와 설정을 분리해서 관리하는 데 가장 기본이 되는 두 객체예요. 이 글에서는 이 둘이 왜 필요한지, 어떻게 생겼는지, 그리고 실전에서 어떻게 쓰는지 하나씩 살펴볼게요.
왜 설정을 코드 밖으로 빼야 할까
애플리케이션을 개발할 때, 같은 코드를 내 컴퓨터(개발 환경)에서도 돌리고 클라우드(실제 트래픽을 처리하는 환경)에서도 돌려야 하는 상황을 떠올려 봐요. 예를 들어 코드가 DATABASE_HOST라는 환경 변수를 읽도록 작성했다고 합시다. 로컬에서는 이 변수를 localhost로 두고, 클라우드에서는 클러스터 안의 데이터베이스 컴포넌트를 노출하는 쿠버네티스 서비스를 가리키게 하면 돼요. 이렇게 하면 클라우드에서 돌고 있는 컨테이너 이미지를 내려받아 로컬에서도 똑같은 코드를 디버깅할 수 있어요.
핵심은 이겁니다. 환경에 따라 달라지는 설정값을 컨테이너 이미지 안에 박아 넣지 말고 밖으로 분리하는 것. 그래야 이미지는 어디서든 그대로 재사용할 수 있고(포트터블), 설정만 바꿔서 환경을 갈아끼울 수 있어요. 이렇게 코드와 설정을 분리하는 동기는 더 깊이 있게 보려면 The Twelve-Factor App을 읽어보세요.
ConfigMap
ConfigMap은 민감하지 않은(non-confidential) 데이터를 키-값(Key-Value) 쌍으로 저장하는 API 객체예요. 파드는 ConfigMap을 세 가지 방식으로 소비할 수 있는데요, 환경 변수, 커맨드라인 인자, 또는 볼륨 안의 설정 파일로요.
ConfigMap은 설정 데이터를 애플리케이션 코드와 분리해서 관리하고 싶을 때 쓰는 객체입니다. "비밀값은 아니지만 환경마다 값이 달라지는 설정"을 담는 곳이라고 이해하면 돼요.
주의: ConfigMap은 비밀성(secrecy)이나 암호화를 제공하지 않아요. 저장하려는 데이터가 기밀이라면 ConfigMap 대신 Secret을 쓰거나, 별도의(서드파티) 도구로 데이터를 보호해야 해요.
ConfigMap 객체의 구조
대부분의 쿠버네티스 객체는 spec 필드를 갖는데, ConfigMap은 조금 달라요. data와 binaryData 필드를 가지는데, 둘 다 키-값 쌍을 값으로 받아요. 둘 다 선택적(optional) 필드입니다.
data: UTF-8 문자열을 담도록 설계된 필드binaryData: 바이너리 데이터를 base64로 인코딩한 문자열로 담도록 설계된 필드
ConfigMap의 이름은 반드시 유효한 DNS 서브도메인 이름이어야 하고, data 또는 binaryData 아래의 각 키는 영숫자 문자, -, _, .로만 구성돼야 해요. 그리고 data의 키는 binaryData의 키와 겹치면 안 됩니다.
v1.19부터는 ConfigMap 정의에 immutable 필드를 추가해서 불변(immutable) ConfigMap을 만들 수 있어요.
ConfigMap 생성 예시
파드의 spec은 ConfigMap을 참조해서 그 데이터를 바탕으로 컨테이너를 설정할 수 있는데, 이때 파드와 ConfigMap은 반드시 같은 네임스페이스에 있어야 해요. 참고로 스태틱 파드(static Pod)의 spec은 ConfigMap이나 다른 API 객체를 참조할 수 없어요.
다음은 단일 값으로 된 키도 있고, 값이 설정 파일 형식의 조각처럼 보이는 키도 있는 ConfigMap 예시입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# property-like keys; 각 키는 단순한 값에 매핑
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# file-like keys
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
이런 ConfigMap을 써서 파드 안의 컨테이너를 설정하는 방법은 네 가지가 있어요.
- 컨테이너의 커맨드와 인자(
command/args) 안에서 - 컨테이너의 환경 변수로
- 애플리케이션이 읽을 수 있도록 읽기 전용 볼륨에 파일로 추가
- 파드 안에서 돌아가는 코드가 쿠버네티스 API로 ConfigMap을 직접 읽기
앞의 세 가지 방법에서는 kubelet이 파드의 컨테이너를 띄울 때 ConfigMap의 데이터를 사용해요. 네 번째 방법은 ConfigMap과 그 데이터를 읽는 코드를 직접 작성해야 하지만, 쿠버네티스 API를 직접 쓰기 때문에 ConfigMap이 변경될 때마다 업데이트를 구독해서 반응할 수 있고, 같은 네임스페이스가 아니라 다른 네임스페이스의 ConfigMap에도 접근할 수 있다는 장점이 있어요.
파드에서 볼륨으로 ConfigMap 사용하기
- ConfigMap을 만들거나 기존 것을 사용해요. 여러 파드가 같은 ConfigMap을 참조할 수 있어요.
- 파드 정의에
.spec.volumes[]아래에 볼륨을 추가하고,.spec.volumes[].configMap.name필드를 참조할 ConfigMap 객체로 설정해요. - ConfigMap이 필요한 각 컨테이너에
.spec.containers[].volumeMounts[]를 추가해요..spec.containers[].volumeMounts[].readOnly = true로 두고, ConfigMap이 나타나길 원하는 안 쓰는 디렉터리 이름으로.spec.containers[].volumeMounts[].mountPath를 지정해요. - 프로그램이 그 디렉터리에서 파일을 찾도록 이미지나 커맨드라인을 수정해요. ConfigMap
data의 각 키는mountPath아래의 파일 이름이 돼요.
ConfigMap을 볼륨으로 마운트하는 파드 예시입니다.
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
configMap:
name: myconfigmap
각 ConfigMap은 .spec.volumes에 참조돼야 해요. 파드에 컨테이너가 여러 개라면 각 컨테이너마다 자기만의 volumeMounts 블록이 필요하지만, .spec.volumes는 ConfigMap 하나당 하나만 있으면 됩니다.
앞서 본 game-demo 예시에서 볼륨을 정의하고 demo 컨테이너에 /config로 마운트하면, ConfigMap에는 키가 네 개인데도 파일은 /config/game.properties와 /config/user-interface.properties 두 개가 생겨요. 그 이유는 파드 정의의 volumes 섹션에 items 배열을 지정했기 때문이에요. items 배열을 아예 생략하면 ConfigMap의 모든 키가 키 이름과 같은 이름의 파일이 되어 네 개의 파일이 생깁니다.
ConfigMap은 한 줄짜리 프로퍼티 값과 여러 줄짜리 파일 형식의 값을 구분하지 않아요. 중요한 건 파드나 다른 객체가 그 값을 어떻게 소비하느냐입니다.
마운트된 ConfigMap은 자동으로 갱신돼요
볼륨에서 현재 소비 중인 ConfigMap이 업데이트되면, 프로젝션된 키도 결국엔 업데이트돼요. kubelet은 주기적인 동기화마다 마운트된 ConfigMap이 최신 상태인지 확인하는데, 현재 값을 가져올 때는 kubelet의 로컬 캐시를 사용합니다. 캐시의 종류는 KubeletConfiguration 구조체의 configMapAndSecretChangeDetectionStrategy 필드로 설정할 수 있는데, 기본값은 watch 방식이에요. ttl 방식이나 모든 요청을 API 서버로 직접 리다이렉트하는 방식도 있습니다.
그래서 ConfigMap이 업데이트된 시점부터 새 키가 파드에 프로젝션되는 시점까지의 총 지연 시간은 kubelet 동기화 주기 + 캐시 전파 지연 시간만큼 될 수 있어요. 여기서 캐시 전파 지연은 선택한 캐시 종류에 따라 달라집니다(watch 전파 지연, 캐시의 ttl, 또는 0에 해당).
반면 환경 변수로 소비되는 ConfigMap은 자동으로 갱신되지 않아서 파드를 재시작해야 해요. 그리고 ConfigMap을 subPath 볼륨 마운트로 사용하는 컨테이너는 ConfigMap 업데이트를 받지 못해요.
ConfigMap을 환경 변수로 사용하기
- 파드 명세의 각 컨테이너에 대해, 쓰고 싶은 각 ConfigMap 키를
env[].valueFrom.configMapKeyRef필드에 환경 변수로 추가해요. - 프로그램이 지정된 환경 변수에서 값을 찾도록 이미지나 커맨드라인을 수정해요.
username과 access_level 두 개의 프로퍼티를 담은 ConfigMap(myconfigmap.yaml) 예시입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: myconfigmap
data:
username: k8s-admin
access_level: "1"
다음 명령으로 ConfigMap 객체를 만들 수 있어요.
kubectl apply -f myconfigmap.yaml
이 ConfigMap의 내용을 환경 변수로 소비하는 파드는 다음과 같아요.
apiVersion: v1
kind: Pod
metadata:
name: env-configmap
spec:
containers:
- name: app
command: ["/bin/sh", "-c", "printenv"]
image: busybox:latest
envFrom:
- configMapRef:
name: myconfigmap
envFrom 필드는 쿠버네티스가 그 안에 중첩된 소스에서 환경 변수를 만들도록 지시해요. 안쪽의 configMapRef는 이름으로 ConfigMap을 참조하며 그 키-값 쌍 전체를 선택합니다.
때로는 파드가 ConfigMap의 모든 값이 아니라 일부만 필요할 수도 있어요. 예를 들어 ConfigMap에서 username 값만 쓰는 다른 파드가 있다고 합시다. 이 경우 env.valueFrom 문법을 쓰면 ConfigMap 안의 개별 키를 선택할 수 있어요. 환경 변수의 이름은 ConfigMap 안의 키와 달라도 됩니다.
apiVersion: v1
kind: Pod
metadata:
name: env-configmap
spec:
containers:
- name: envars-test-container
image: nginx
env:
- name: CONFIGMAP_USERNAME
valueFrom:
configMapKeyRef:
name: myconfigmap
key: username
이 매니페스트로 만든 파드에서는 환경 변수 CONFIGMAP_USERNAME이 ConfigMap의 username 값으로 설정되는 걸 볼 수 있어요. ConfigMap 데이터의 다른 키는 환경에 복사되지 않습니다.
크기 제한
ConfigMap은 큰 데이터 덩어리를 담도록 설계되지 않았어요. ConfigMap에 저장되는 데이터는 1 MiB를 초과할 수 없습니다. 이 제한보다 큰 설정을 저장해야 한다면 볼륨을 마운트하거나 별도의 데이터베이스, 파일 서비스를 고려해보세요.
불변 ConfigMap (Immutable ConfigMap)
Immutable Secrets and ConfigMaps 기능은 쿠버네티스 v1.21부터 Stable입니다. 개별 Secret과 ConfigMap을 불변으로 설정할 수 있는 옵션을 제공해요. ConfigMap을 대규모로(수만 개 이상의 고유한 ConfigMap-파드 마운트) 사용하는 클러스터에서 데이터 변경을 막으면 다음과 같은 이점이 있어요.
- 의도치 않은(또는 원치 않는) 업데이트로 인한 애플리케이션 장애를 막아줘요
- 불변으로 표시된 ConfigMap의 watch를 닫아서 kube-apiserver의 부하를 크게 줄여 클러스터 성능을 개선해요
immutable 필드를 true로 설정하면 불변 ConfigMap을 만들 수 있어요.
apiVersion: v1
kind: ConfigMap
metadata:
...
data:
...
immutable: true
ConfigMap이 불변으로 표시되면 이 변경을 되돌릴 수도 없고, data나 binaryData의 내용도 변경할 수 없어요. 오직 ConfigMap을 삭제하고 다시 만드는 것만 가능합니다. 기존 파드는 삭제된 ConfigMap에 대한 마운트 포인트를 유지하므로, 이런 파드는 다시 생성해 주는 것이 좋아요.
Secret
Secret은 비밀번호, 토큰, 키 같은 소량의 민감한(sensitive) 데이터를 담는 객체예요. 이런 정보는 원래 파드 명세나 컨테이너 이미지에 넣을 수도 있는데, Secret을 쓰면 그럴 필요가 없어요. Secret은 이를 사용하는 파드와 독립적으로 만들 수 있기 때문에, 파드를 만들고 보고 수정하는 워크플로 과정에서 Secret(과 그 데이터)이 노출될 위험이 적어요. 쿠버네티스와 클러스터에서 돌아가는 애플리케이션은 비휘발성 저장소에 민감한 데이터를 쓰지 않는 등 Secret에 대해 추가적인 예방 조치를 취할 수도 있어요.
Secret은 ConfigMap과 비슷하지만 기밀 데이터를 담기 위한 용도라는 점이 다릅니다.
주의: 쿠버네티스 Secret은 기본적으로 API 서버의 데이터 저장소(etcd)에 암호화되지 않은 상태로 저장돼요. API 접근 권한이 있는 사람은 누구나 Secret을 조회하거나 수정할 수 있고, etcd에 접근할 수 있는 사람도 마찬가지예요. 게다가 네임스페이스에서 파드를 생성할 권한이 있는 사람은 그 권한을 이용해 해당 네임스페이스의 어떤 Secret이든 읽을 수 있어요. 여기에는 Deployment를 만들 수 있는 능력 같은 간접적인 접근도 포함됩니다.
Secret을 안전하게 사용하려면 최소한 다음 단계를 거쳐야 해요.
- Secret에 대해 저장 시 암호화(Encryption at Rest) 를 활성화해요.
- Secret에 대한 최소 권한(least-privilege)의 RBAC 규칙을 활성화하거나 구성해요.
- Secret 접근을 특정 컨테이너로 제한해요.
- 외부 Secret 저장소 제공자(external Secret store providers) 사용을 고려해요.
Secret 관리와 보안을 개선하는 더 자세한 지침은 Good practices for Kubernetes Secrets를, 더 자세한 내용은 Information security for Secrets를 참고하세요.
Secret의 용도
Secret은 다음과 같은 용도로 쓸 수 있어요.
- 컨테이너의 환경 변수 설정
- 파드에 SSH 키나 비밀번호 같은 자격 증명(credentials) 제공
- kubelet이 프라이빗 레지스트리에서 컨테이너 이미지를 가져오도록 허용
쿠버네티스 컨트롤 플레인도 Secret을 사용해요. 예를 들어 부트스트랩 토큰(bootstrap token) Secret은 노드 등록을 자동화하는 메커니즘을 돕는 데 쓰입니다.
활용 사례: 시크릿 볼륨 안의 dotfile
키 이름을 점(dot)으로 시작하도록 정의하면 데이터를 "숨길" 수 있어요. 이 키는 dotfile(또는 "hidden" 파일)을 나타내는데요. 예를 들어 다음 Secret이 secret-volume 볼륨에 마운트되면, 볼륨에는 .secret-file이라는 단일 파일이 생기고 dotfile-test-container는 /etc/secret-volume/.secret-file 경로에 이 파일을 갖게 돼요.
apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret:
secretName: dotfile-secret
containers:
- name: dotfile-test-container
image: registry.k8s.io/busybox
참고: 점 문자로 시작하는 파일은
ls -l출력에서 숨겨져요. 디렉터리 내용을 나열할 때는ls -la를 써야 보입니다.
정리
- 설정값을 코드 밖으로: ConfigMap과 Secret은 환경에 따라 달라지는 설정을 코드·이미지에서 분리해서 관리하게 해줘요.
- ConfigMap: 민감하지 않은 설정을 키-값으로 저장. 환경 변수, 커맨드라인 인자, 볼륨 파일, API 직접 읽기 네 가지 방식으로 소비 가능. 1 MiB 제한.
- Secret: 비밀번호·토큰·키 같은 민감한 데이터용. ConfigMap과 구조가 비슷하지만 기본이 비암호화(etcd) 저장이므로 보안 조치가 필요해요.
다음으로는 Secret과 파드에서 ConfigMap 사용하기 관련 문서를 읽어보는 걸 추천해요.