Kubernetes ConfigMap & Secret, 제대로 이해하기
본 포스팅에서는 Kubernetes의 환경변수를 구성하는 방법인 ConfigMap & Secret 의 개념을 이해하고 적용할 수 있도록 하는 목표를 가집니다.
🔗 Kubernetes Series
모든 Kubernetes 시리즈를 확인하시려면 위를 참고해 주세요.
쿠버네티스를 사용하면서, 환경에 따라 다르거나 자주 변경되는 설정 옵션들을,
실행되는 애플리케이션 코드와 분리시키고 싶을 때가 생깁니다.
환경 변수를 정의하면서 이를 해결할 수 있는데요.
본 포스팅에서는 쿠버네티스에서 환경변수를 설정할 수 있는
총 세가지의 방법을 소개해드리고자 합니다.
Environment Variables
Container Runtime 중 대표적인 Docker에서 환경변수를 입력할 수 있습니다.
Dockerfile에서 ENV 지시자를 통해 환경변수를 정의할 수 있는데요.
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
혹은, Command Line에서 실행 시 옵션 값으로 입력할 수도 있습니다.
docker run -e <<ENV_KEY>>=<<ENV_VALUE>> <<container_name>>
# ex> docker run -e DEMO_GREETING="Hello from the environment" my-app
Kubernetes 에서도 이처럼 환경 변수를 지정할 수 있습니다.
각각의 Pod에 환경변수를 지정해주는 것이죠.
아래와 같이 세 개의 방식을 통해 환경 변수를 지정할 수 있습니다.
✔️ Plain Key Value
✔️ ConfigMap
✔️ Secrets
하나씩 살펴보도록 하겠습니다.
#1. Plain Key Value
가장 간단한 방식으로 환경 변수를 설정하기 위한 속성을 직접 Pod 정의 파일에 정의합니다.
이때, Pod 정의 파일 내 spec.containers[].env
속성을 사용할 수 있습니다.
apiVersion: v1
kind: Pod
metadata:
name: print-greeting
spec:
containers:
- name: env-print-demo
image: bash
env:
- name: GREETING
value: "Warm greetings to"
- name: HONORIFIC
value: "The Most Honorable"
- name: NAME
value: "Kubernetes"
- name: MESSAGE
value: "$(GREETING) $(HONORIFIC) $(NAME)"
command: ["echo"]
args: ["$(MESSAGE)"]
env
속성은 배열 타입으로, 하위에 각 name과 value 리스트를 명시합니다.
env[].name
: 환경 변수 이름
env[].value
: 해당 환경 변수 값
이 때, ConfigMap Key는 DNS subdomain 상 유효한 값이어야 합니다.
가령, alphanumeric characters, dashes, underscores, dots 등을 포함하며,
유효하지 않은 값들이 무엇인지 살펴볼 필요가 있습니다.
혹은 선택적으로 Key는 leading dot (ex. .local.test.com
)이 포함될 수 있습니다.
쉽고 간단히 사용할 수 있지만,
여러 Pod에 지정하거나 정의하고자 하는 환경 변수가 많아질 수록,
한 파일에 여러 환경 변수를 관리하기 어려워집니다.
혹은, 조금 더 독립적으로 정의하고 싶을 때가 생깁니다.
그럴 땐, ConfigMap이나 Secret의 값을 가져올 수 있습니다.
#2. ConfigMap
환경 변수를 Pod 정의 파일에서 분리해 Configuration Map 형식으로 중앙 관리할 수 있습니다.
ConfigMap은 쿠버네티스에서 Key-Value 쌍의 Config 데이터를 전달하는 데 사용합니다.
Pod는 ConfigMap을 이름으로 참조하기 때문에,
모든 Pod 스펙을 동일하게 사용하면서 각 환경에서 다른 구성을 사용할 수 있습니다.
ConfigMap 객체 생성을 위해서는 아래 두 단계를 진행합니다.
STEP 1. Create ConfigMap
STEP 2. Inject into Pods
STEP 1. Create ConfigMap
첫째, ConfigMap을 생성합니다.
다른 쿠버네티스 객체와 동일하게, Imperative 혹은 Declarative 방법으로 생성 가능합니다.
1. Imperative: kubectl create
첫 번째로, Key-Value 를 Command Line에 바로 입력하여 적용할 수 있습니다.
사용할 수 있는 옵션들에 대해 미리 살펴보면 아래와 같습니다.
$ kubectl create configmap my-config
--from-file=foo.json ❶
--from-file=bar=foobar.conf ❷
--from-file=config-opts/ ❸
--from-literal=some=thing ❹
❶ 단일 파일
❷ 특정 키에 파일에 지정된 값 바로 입력
❸ 디렉터리 하위 전체
❹ 리터럴: 값 직접 입력
하나씩 살펴보도록 하겠습니다.
✔️ --from-literal
--from-literal
옵션을 사용하면 원하는 환경 변수 key-value 값을 입력할 수 있습니다.
❯ kubectl create configmap \
<<config-name>> --from-literal=<key>=<value>
가령, 아래처럼 적용할 수 있습니다.
❯ kubectl create configmap myconfigmap \
--from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
configmap/myconfigmap created
여러 개의 옵션을 사용하고 싶다면, 위 처럼 --from-literal
옵션을 여러 번 사용할 수 있습니다.
실제 생성된 ConfigMap 객체를 확인해보면, data 섹션에서 해당 데이터를 확인할 수 있습니다.
❯ kubectl get configmap myconfigmap -o yaml
apiVersion: v1
data:
bar: baz
foo: bar
one: two
kind: ConfigMap
metadata:
creationTimestamp: "2024-04-16T12:42:36Z"
name: myconfigmap
namespace: default
resourceVersion: "1248496"
uid: 25cc5f7e-e228-4d13-b8bf-662a1ff1b316
✔️ --from-file
--from-literal
옵션을 사용하면 명령줄이 길어질 수 있는데,
이때 --from-file
옵션을 통해 파일을 명시할 수 있습니다.
❯ kubectl create configmap \
<<config-name>> --from-file=<path-to-file>
이 때, 인자 값으로는 위에서 보았듯이, 단일 파일 혹은 디렉터리를 입력할 수 있습니다.
미리 생성해둔 파일을 명시합니다.
❯ kubectl create configmap fortune-config \
--from-file=my-nginx-config.conf \
--from-file=sleep-interval=sleep-interval-config.conf
configmap "fortune-config" created
실제 생성된 데이터를 확인해보면 다음과 같습니다.
❯ 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: |
5
kind: ConfigMap
metadata:
creationTimestamp: "2024-04-16T13:30:13Z"
name: fortune-config
namespace: default
resourceVersion: "1251054"
uid: 371cfd85-873c-4bb9-98a4-8c943f2f9979
2. Declarative: kubectl apply
혹은, 미리 정의해둔 파일을 kubectl apply
등 명령어를 통해 생성할 수 있습니다.
ConfigMap 정의 파일은 보통 객체 정의에서 보았던 spec 대신 data 속성을 포함합니다.
# config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_COLOR: blue
APP_MODE: prod
Config 파일 이름 지정해서 kubectl 명령 실행합니다.
kubectl apply -f config-map.yaml
그럼 쿠버네티스는 지정한 ConfigMap 값으로 환경 변수를 생성합니다.
FYI. View ConfigMaps
ConfigMap을 확인하고 싶다면, kubectl get configmaps
명령 실행합니다.
혹은, 단축어로 kubectl get cm
을 사용할 수 있습니다.
❯ kubectl get configmaps
NAME DATA AGE
app-config 2 5s
kube-root-ca.crt 1 48d
더 자세한 내용을 보기 위해선, kubectl describe configmaps
명령 실행해서 상세 확인이 가능합니다.
Data 섹션 하위에 지정한 환경 변수를 확인할 수 있습니다.
❯ kubectl describe configmaps
Name: app-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
APP_COLOR:
----
blue
APP_MODE:
----
prod
BinaryData
...
STEP 2. Inject into Pod
정의를 했다면, 이제 Pod에 적용할 환경변수를 지정합니다.
이때, 환경 변수를 지정하는 방식에는 세 가지가 존재합니다.
✔️ 첫 번째, envFrom - ConfigMapRef
...
spec:
containers:
- name: something
...
envFrom:
- configMapRef:
name: app-config
.spec.containers[]
하위에 envFrom[].configMapRef
로 이미 생성해둔 ConfigMap 을 참조합니다.
envFrom 속성은 Array 타입으로 다중의 환경 변수를 전달할 수 있습니다.
✔️ 두 번째, ConfigMap entry 값을 Env에 직접 정의
...
spec:
containers:
- image: something
env:
- name: INTERVAL # 정의할 ENV 이름
valueFrom:
configMapKeyRef:
name: fortune-config # ConfigMap 이름
key: sleep-interval # ConfigMap Entry Key 값
...
미리 정의해둔 ConfigMap에 정의된 속성 중 원하는 값을 Key으로 참고해 다른 ENV 이름으로 정의합니다.
다소 복잡해보일 수 있는데, 아래 그림이 이해에 도움이 될 듯 합니다.
✔️ 세 번째, Volume 지정
...
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
...
- name: config
mountPath: /etc/nginx/conf.d # configMap volume 에 마운팅할 포인트 지정
readOnly: true
...
volumes:
...
- name: config
configMap: # ConfigMap 리스트 중,
name: fortune-config # fortune-config 객체를 들고옴
파일을 볼륨에 입력한 후, 마운팅 포인트에서 해당 값을 참조합니다.
추가 옵션:
.spec.containers[].volumeMounts[].readOnly = true
#3. Secrets
하지만, ConfigMap만으로 모든 요구사항을 채우기 어렵습니다.
간혹, 애플리케이션에서 직접 입력할 수 없는 Secret 값을 설정해야합니다.
예를 들어, Database Host, User, Password 등을 설정할 때 어떻게 설정해야 할까요?
ConfigMap은 일반 텍스트 형식으로 구성 데이터를 저장하기 때문에 적절하지 않습니다.
이때, Secret 사용 - 비밀번호나 Key 등 민감한 정보를 저장하는 데 활용할 수 있습니다.
값은 아래와 같이 인코딩된 형식으로 저장됩니다.
DB_Host: RvEtdlSw=
DB_User: THevDhS==
DB_Password: aHrGgsdJ
이렇게 인코딩되어 저장된다는 점만 제외하고는,
ConfigMap 와 거의 동일한 방식으로 설정할 수 있습니다.
ConfigMap 와 마찬가지로 Secret 구성은 두 단계로 구성됩니다.
STEP 1. Create Secret
STEP 2. Inject into Pods
STEP 1. Create Secret
가령, 아래와 같은 설정 값을 Secret으로 저장한다고 가정해봅시다.
DB_Host: mysql
DB_User: root
DB_Password: paswrd
ConfigMap 과 동일하게 Imperative 와 Declarative 방식으로 생성할 수 있습니다.
1. Imperative
kubectl create secret generic 명령어를 통해 Secret 지정 가능합니다.
kubectl create secret generic \
<secret-name> --from-literal=<key>=<value>
예를 들면, 아래와 같이 정의할 수 있습니다.
kubectl create secret generic \
app-secret --from-literal=DB_Host=mysql \
--from-literal=DB_User=root \
--from-literal=DB_Password=paswrd
혹은 --from-file 옵션으로 파일을 지정할 수 있습니다.
kubectl create secret generic \
<secret-name> --from-file=<path-to-file>
가령 아래와 같이 정의할 수 있습니다.
kubectl create secret generic \
app-secret --from-file=app_secret.properties
모든 내용은 ConfigMap에서 지정한 내용과 동일하게 적용할 수 있기 때문에,
--from-literal
과 --from-file
부분은 ConfigMap의 Step 1 섹션을 참고해주시길 바랍니다.
2. Declarative
혹은, 미리 정의해둔 파일을 kubectl apply
등 명령어를 통해 생성할 수 있습니다.
Secret 정의 파일은 보통 객체 정의에서 보았던 spec 대신 data 속성을 포함합니다.
apiVersion: v1
kind: Secret
metadata:
name: app-secret
data:
DB_Host: bXlzcWw=
DB_User: cm9vdA==
DB_Password: cGFzd3Jk
Secret 은 민감한 데이터를 저장하기 위해 사용되어 인코딩된 포맷으로 저장합니다.
명령적 방식 Secret을 생성하려면 인코딩된 형식의 Secret 값을 지정해야 합니다.
일반 텍스트에서 인코딩된 형식으로 데이터를 변환하려면 echo -n '<<string>>' | base64
입력할 수 있습니다.
❯ echo -n 'mysql' | base64
bXlzcWw=
❯ echo -n 'root' | base64
cm9vdA==
❯ echo -n 'paswrd' | base64
cGFzd3Jk
위와 같이 정의한 후, 아래 명령어를 통해 Secret 객체를 생성합니다.
kubectl create -f secret-data.yaml
FYI. View Secrets
secret을 확인을 위해서는 kubectl get secrets 명령어 사용합니다.
❯ kubectl get secrets
NAME TYPE DATA AGE
app-secret Opaque 3 4s
Name: app-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
DB_Host: 5 bytes
DB_Password: 6 bytes
DB_User: 4 bytes
값을 보고 싶다면, YAML 포맷으로 확인할 수 있습니다.
❯ kubectl get secrets app-secret -o yaml
apiVersion: v1
data:
DB_Host: bXlzcWw=
DB_Password: cGFzd3Jk
DB_User: cm9vdA==
kind: Secret
metadata:
name: app-secret
namespace: default
...
인코딩의 반대로, 디코딩을 하고 싶다면 --decode
옵션을 추가해서 확인할 수 있습니다.
❯ echo -n 'bXlzcWw=' | base64 --decode
mysql
❯ echo -n 'cm9vdA==' | base64 --decode
root
❯ echo -n 'cGFzd3Jk' | base64 --decode
paswrd
STEP 2. Inject into Pod
Pod에 생성한 Secret을 입력하는 방법은 ConfigMap과 동일합니다.
✔️ 첫 번째, envFrom - ConfigMapRef
.spec.containers[]
하위에 envFrom[].secretRef
로, 이미 생성해둔 Secret 을 참조합니다.
envFrom 속성은 Array 타입으로 다중의 환경 변수를 전달할 수 있습니다.
...
spec:
containers:
- name: something
...
envFrom:
- secretRef:
name: app-secret
configMapRef 과 비교해보면 configMapRef 이 secretRef 로만 변경되었습니다.
✔️ 두 번째, Secret entry 값을 Env에 직접 정의
...
spec:
containers:
- image: something
env:
- name: DB_Password # 정의할 ENV 이름
valueFrom:
secretKeyRef:
name: app-secret # Secret 이름
key: DB_Password # Secret Entry Key 값
...
미리 정의해둔 Secret 에 정의된 속성 중 원하는 값을 Key으로 참고해 다른 ENV 이름으로 정의합니다.
다소 복잡해보일 수 있는데, 아래 그림은 ConfigMap이지만 동일한 구조이기 때문에,
이해에 도움을 드리고자 첨부했습니다.
✔️ 세 번째, Volume 지정
apiVersion: v1
kind: Pod
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret: # Secret 객체 리스트 중,
secretName: dotfile-secret # dotfile-secret 객체를 들고옴
containers:
- name: dotfile-test-container
image: registry.k8s.io/busybox
command:
- ls
- "-l"
- "/etc/secret-volume"
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume" # Secret volume 에 마운팅할 포인트 지정
파일을 볼륨에 입력한 후, 마운팅 포인트에서 해당 값을 참조합니다.
추가 옵션:
.spec.containers[].volumeMounts[].readOnly = true
⚠️ Caution
Kubernetes Secret은 기본적으로 API 서버의 기본 데이터 저장소(etcd)에 암호화되지 않은 상태로 저장됩니다.
암호화되어 있지 않기 때문에, 누구든 기밀문서로 만든 파일을 볼 수 있고 Secret 을 얻을 수 있습니다.
가령, API 액세스 권한이 있는 사람이나 etcd에 대한 액세스 권한이 있는 사람은
누구나 Secret을 검색하거나 수정할 수 있습니다.
또한, 네임스페이스에 Pod를 생성할 권한이 있는 사람은 누구나
해당 액세스 권한을 사용하여 해당 네임스페이스에 있는 보안 비밀을 읽을 수 있습니다.
Secret을 안전하게 사용하려면 최소한 다음을 고려해야 합니다.
- 비밀에 대한 저장 데이터 암호화를 활성화.
- 보안 비밀에 대한 최소 권한 액세스로 RBAC 규칙을 활성화하거나 구성.
- 특정 컨테이너에 대한 Secret 액세스 제한
- 외부 Secret Store Providers 사용 고려: AWS Provider, Azure Provider, GCP Provider, Vault Provider
그럼 여기까지, 쿠버네티스의 Secret에 대해 알아보았습니다.
| Reference |
🔗 Kubernetes.io - Official Docs
🔗 Kubernetes in Action by Marko Luksa
🔗 Udemy: certified-kubernetes-administrator-with-practice-tests