설정은 어떻게 애플리케이션까지 전달될까?환경변수명령으로 컨피그맵 만들어서 사용해보기설정파일로 컨피그맵 사용하기ENV 파일로 컨피그맵 만들기우선순위가 다르게 부여된 출처 별로 설정값 읽어들이기컨피그맵에 담긴 설정 데이터 주입하기여러 개의 설정을 하나의 컨피그맵에만약 마운트 경로를 잘못 설정하면 어떻게 될까비밀값을 이용해 민감한 정보 다루기명령행으로 비밀값 만들기yaml로 비밀값 만들기파일로 전달하기쿠버네티스 애플리케이션 설정 관리
쿠버네티스의 장점은 다양한 환경 간 차이를 없앨 수 있다는 것이다. 단, 환경별로 설정값은 다르기 때문에, 설정값을 주입해야하는데, 이때 사용할 수 있는 리소스에는 컨피그맵(ConfigMap)과 비밀값(Secret)이 있다.
설정은 어떻게 애플리케이션까지 전달될까?
컨피그맵과 비밀값은 스스로 어떤 기능을 하지는 않는다. 다만, 적은 양의 데이터를 저장할 뿐이다. 이런 설정은 파드로 전달되어 컨테이너 환경의 일부가 되는데, 이를 통해 저장된 데이터를 읽을 수 있게 된다. 가장 기본이 되는 환경변수부터 알아보자.
환경변수
간단히 이전 장에서 만들었던 sleep 디플로이먼트로 실험하자. 일단은 별도 설정 없이 그냥 만들어보자!
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
% kubectl apply -f sleep/sleep.yaml
deployment.apps/sleep created
# 파드가 준비될 때까지 대기
% kubectl wait --for=condition=Ready pod -l app=sleep
pod/sleep-568fb49bb7-khj6v condition met
# 파드 속 컨테이너에 설정된 환경변수 값 확인
% kubectl exec deploy/sleep -- printenv HOSTNAME KIAMOL_CHAPTER
sleep-568fb49bb7-khj6v
command terminated with exit code 1
printenv
는 환경변수의 값을 출력하는 리눅스 명령인데, HOSTNAME
이라는 환경변수는 쿠버네티스에서 파드 이름으로 정의해주는 환경변수이기 때문에 출력되었지만, KIAMOL_CHAPTER
환경변수는 정의되지 않은 상태였으므로 오류코드와 함께 종료되었다.이번에는 환경변수 설정을 해보자.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
# 환경변수 정의하기
env:
- name: KIAMOL_CHAPTER
value: "04"
# 기존 디플로이먼트를 업데이트한다.
# 환경변수가 추가되면서, 파드 정의가 변경되었으므로 기존 파드가 새로운 파드로 교체된다.
% kubectl apply -f sleep/sleep-with-env.yaml
deployment.apps/sleep configured
# 환경변수를 출력해보면 KIAMOL_CHAPTER도 잘 나오는 것을 볼 수 있다.
% kubectl exec deploy/sleep -- printenv HOSTNAME KIAMOL_CHAPTER
sleep-675cc588c6-xx8vb
04
이처럼 간단한 설정이라면 파드 정의에 포함시켜도 나쁘지 않겠지만, 실제 설정값은 이보다 훨씬 복잡하다. 이런 경우 ConfigMap을 이용할 수 있다.
명령으로 컨피그맵 만들어서 사용해보기
컨피그맵은 파드에서 읽어올 수 있는 데이터를 저장하는 리소스다. 지원하는 데이터 유형으로는 단순한 key-value부터 텍스트, 바이너리 파일까지 다양하다. key-value는 환경변수 형태로 주입할 수 있고, 텍스트를 저장했다면 다양한 확장자를 가지는 설정파일을 파드에 전달할 수 있다. 하나의 파드에 여러 개의 컨피그맵을 전달할 수 있고, 하나의 컨피그맵을 여러 파드로 전달할 수도 있다.
그러면 컨피그맵을 만들어서 파드에 적용시켜보자!
# 명령으로 sleep-config-literal 이라는 이름을 가지는 컨피그맵을 생성한다.
# kiamol.section = 4.1
% kubectl create configmap sleep-config-literal --from-literal=kiamol.section='4.1'
configmap/sleep-config-literal created
# 컨피그맵을 확인한다.
% kubectl get cm sleep-config-literal
NAME DATA AGE
sleep-config-literal 1 12s
# 상세정보를 보기 좋게 출력한다.
% kubectl describe cm sleep-config-literal
Name: sleep-config-literal
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
kiamol.section:
----
4.1
BinaryData
====
Events: <none>
# 정의가 수정된 파드를 배치한다.
% kubectl apply -f sleep/sleep-with-configMap-env.yaml
deployment.apps/sleep configured
# 환경변수가 적용되었음을 볼 수 있다.
% kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'
KIAMOL_SECTION=4.1
KIAMOL_CHAPTER=04
위에서 쓰인 디플로이먼트 매니페스트는 다음과 같다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
env:
- name: KIAMOL_CHAPTER
value: "04"
- name: KIAMOL_SECTION
valueFrom:
configMapKeyRef:
name: sleep-config-literal # configmap의 이름을 지정한다.
key: kiamol.section # kiamol.seciton 이라는 키의 값으로부터 가져온다.
설정파일로 컨피그맵 사용하기
쿠버네티스는 우리가 사용할 만한 대부분의 형태를 지원한다. 먼저는 env 파일을 살펴보자.
ENV 파일로 컨피그맵 만들기
KIAMOL_CHAPTER=ch04 KIAMOL_SECTION=ch04-4.1 KIAMOL_EXERCISE=try it now
# 위 ENV 파일을 이용하여 컨피그맵을 만듭니다.
% kubectl create configmap sleep-config-env-file --from-env-file=sleep/ch04.env
configmap/sleep-config-env-file created
# 컨피그맵을 확인합니다.
% kubectl get cm sleep-config-env-file
NAME DATA AGE
sleep-config-env-file 3 48s
# 컨피그맵을 반영합니다.
% kubectl apply -f sleep/sleep-with-configMap-env-file.yaml
deployment.apps/sleep configured
# 환경변수를 확인합니다.
# KIAMOL_EXERCISE는 위 env 파일대로 적용되었지만,
# KIAMOL_CHAPTER와 KIAMOL_SECTION은 파일과 다른 것을 볼 수 있다.
% kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'
KIAMOL_EXERCISE=try it now
KIAMOL_SECTION=4.1
KIAMOL_CHAPTER=04
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
# 컨피그맵에서 읽어오기
envFrom:
- configMapRef:
name: sleep-config-env-file
# 기존 env 항목
env:
- name: KIAMOL_CHAPTER
value: "04"
- name: KIAMOL_SECTION
valueFrom:
configMapKeyRef:
name: sleep-config-literal
key: kiamol.section
환경변수를 적용했지만, env 파일에 있는 것과는 값이 다른 것을 볼 수 있다.
환경변수의 이름이 중복되는 경우,
env
항목에 정의된 값이 envFrom
항목에 정의된 값보다 우선순위가 높다.우선순위가 다르게 부여된 출처 별로 설정값 읽어들이기
환경변수는 널리 지원되지만, 더 다양한 우선순위를 가진 설정값이 필요한 경우가 많다.
우선 일반적인 설정으로 실행해보기!
apiVersion: v1
kind: Service
metadata:
name: todo-web
spec:
ports:
- port: 8080
targetPort: 80
selector:
app: todo-web
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-web
spec:
selector:
matchLabels:
app: todo-web
template:
metadata:
labels:
app: todo-web
spec:
containers:
- name: web
image: kiamol/ch04-todo-list
env:
- name: Logging__LogLevel__Default
value: Warning
# 위 manifest로 서비스와 디플로이먼트를 실행한다.
% kubectl apply -f todo-list/todo-web.yaml
service/todo-web created
deployment.apps/todo-web created
# 준비까지 대기
% kubectl wait --for=condition=Ready pod -l app=todo-web
pod/todo-web-55dfcd87b9-dpn75 condition met
# 서비스에 접근하기 위한 주소를 출력
% kubectl get svc todo-web -o jsonpath='http://{.status.loadBalancer.ingress[0].*}:8080'
http://localhost:8080
# 해당 주소로 접근해본다.
# http://localhost:8080, http://localhost:8080/config에 접근해보자.
# 지금은 404가 뜬다.
# 서비스 목록 조회
% kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8h
todo-web LoadBalancer 10.110.61.89 localhost 8080:31899/TCP 82s
# 로그 확인
# Warn 수준의 로그가 찍히는 것을 확인할 수 있다.
wonjulee@wonjuui-MacBookAir ch04 % kubectl logs -l app=todo-web
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
warn: ToDoList.Pages.ConfigModel[0]
Attempt to view config settings

쿠버네티스에서는 스프링, NodeJS, 파이썬 등과 같은 애플리케이션에 모두 다음 전략을 사용한다.
- 기본 설정은 이미지에 포함시킨다.
- 각 환경 별 설정값은 컨피그맵에 담겨서 컨테이너의 파일 시스템으로 전달된다. 애플리케이션에서 지정한 경로에 설정파일을 주입하거나, 컨테이너 이미지에 담긴 파일을 덮어쓰는 방식이다.
- 기타 변경이 필요한 설정값은 디플로이먼트 내 파드 정의에서 환경변수 형태로 적용한다.
컨피그맵 정의로 만들어보자.
apiVersion: v1
kind: ConfigMap # 리소스 유형은 컨피그맵이다.
metadata:
name: todo-web-config-dev # 컨피그맵의 이름을 지정한다.
data:
config.json: |- # key-value에서 key 이름인 config.json이 파일 이름이 된다.
{ # 파일 내용은 어떤 포맷이라도 가능하다.
"ConfigController": {
"Enabled" : true
}
}
# 위 파일을 바탕으로 컨피그맵을 생성한다.
% kubectl apply -f todo-list/configMaps/todo-web-config-dev.yaml
configmap/todo-web-config-dev created
# 해당 컨피그맵을 사용하도록 디플로이먼트를 업데이트한다.
% kubectl apply -f todo-list/todo-web-dev.yaml
deployment.apps/todo-web configured
# 웹브라우저에서 /config 새로고침
# 아래과 같은 결과를 얻을 수 있다.

컨피그맵에 담긴 설정 데이터 주입하기
이전 예제에서 다뤘던 디플로이먼트 manifest는 다음과 같다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-web
spec:
selector:
matchLabels:
app: todo-web
template:
metadata:
labels:
app: todo-web
spec:
containers:
- name: web
image: kiamol/ch04-todo-list
volumeMounts: # 컨테이너에 볼륨을 마운트
- name: config # 마운트할 볼륨 이름
mountPath: "/app/config" # 볼륨 마운트 경로
readOnly: true
volumes: # 볼륨은 파드 수준에서 정의한다.
- name: config
configMap: # 볼륨의 원본은 컨피그맵이다.
name: todo-web-config-dev # 내용을 읽어올 컨피그맵 이름
환경변수 이외의 설정값을 전달하는 또 다른 방법은 컨테이너 파일 시스템으로 설정값을 주입하는 것이다. 컨테이너 파일 시스템은 컨테이너 이미지와 그 외의 출처에서 온 파일로 구성되는 가상 구조로, 쿠버네티스는 컨테이너 파일 시스템에 컨피그맵을 추가할 수 있게 해준다.
위에서
/app
경로는 원래 컨테이너 이미지에 있던 경로이고, /app/config
는 컨피그맵 볼륨에서 읽어들인 디렉토리이다. 컨피그맵은 디렉토리로 취급되며, 하위 항목은 파일로 취급된다.# 도커 이미지에 존재하는 설정파일이 존재하는지 확인
% kubectl exec deploy/todo-web -- sh -c 'ls -l /app/app*.json'
-rw-r--r-- 1 root root 469 Jun 26 2022 /app/appsettings.json
# 기본 설정 파일 내용 확인
% kubectl exec deploy/todo-web -- sh -c 'cat /app/app*.json'
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Error"
}
},
"AllowedHosts": "*",
"BannerMessage": "",
"ConfigController": {
"Enabled": false
},
"Database": {
"Provider": "Sqlite",
"ReadOnly": false
},
"ConnectionStrings": {
"ToDoDb": "Filename=todo-list.db",
"ToDoDb-ReadOnly": "Filename=todo-list.db"
},
"Metrics": {
"Enabled": false
}
}
# 추가 설정 파일 확인
% kubectl exec deploy/todo-web -- sh -c 'ls -l /app/config/*.json'
lrwxrwxrwx 1 root root 18 Jun 19 15:08 /app/config/config.json -> ..data/config.json
# 추가 설정 파일 내용 확인
% kubectl exec deploy/todo-web -- sh -c 'cat /app/config/*.json'
{
"ConfigController": {
"Enabled" : true
}
}
# 추가 설정 파일에 값을 쓰려고 하면?
# 읽기 전용임을 확인할 수 있다.
% kubectl exec deploy/todo-web -- sh -c 'echo ch04 >> /app/config/config.json'
sh: can't create /app/config/config.json: Read-only file system
command terminated with exit code 1
여러 개의 설정을 하나의 컨피그맵에
이렇듯 컨피그맵을 디렉토리 형태로 읽어들이기 때문에, 다양한 애플리케이션 설정 방법을 적용할 수 있고, 여러 파일에 존재하더라도 모든 설정을 하나의 컨피그맵으로 관리할 수 있다.
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-web-config-dev
data:
# 기존 설정 파일
config.json: |-
{
"ConfigController": {
"Enabled" : true
}
}
# 두 번째 설정 파일
logging.json: |-
{
"Logging": {
"LogLevel": {
"ToDoList.Pages" : "Debug"
}
}
}
만약, 애플리케이션이 실행 중일 때 컨피그맵을 업데이트하면 어떻게 될까? 그것은 애플리케이션마다 다르다. 어떤 애플리케이션은 시작할 때만 메모리로 읽기 때문에 업데이트된 것을 알지 못한다. 예제에 있는 애플리케이션은 업데이트를 감지하도록 작성돼있다.
# 이전 로깅을 확인합니다.
% kubectl logs -l app=todo-web
# 로깅 관련 업데이트된 설정파일을 반영합니다.
% kubectl apply -f todo-list/configMaps/todo-web-config-dev-with-logging.yaml
configmap/todo-web-config-dev configured
# 설정파일이 애플리케이션에 반영되기까지 여유시간을 두고, 기다립니다.
wonjulee@wonjuui-MacBookAir ch04 % sleep 120
# 설정파일이 올바르게 들어갔는지 확인합니다.
% kubectl exec deploy/todo-web -- sh -c 'ls -l /app/config/*.json'
lrwxrwxrwx 1 root root 18 Jun 19 15:08 /app/config/config.json -> ..data/config.json
lrwxrwxrwx 1 root root 19 Jun 22 12:37 /app/config/logging.json -> ..data/logging.json
# 로깅 설정이 반영되었는지 확인합니다.
% kubectl logs -l app=todo-web
만약 마운트 경로를 잘못 설정하면 어떻게 될까
컨피그맵의 마운트 경로를 잘 설정하는 것은 중요하다. 컨피그맵이 디렉토리로 추가될 때, 만약 디렉토리가 존재하면 없앤 후, 덮어써버리기 때문이다. 아래는 이미지가 있는 경로를 마운트 경로로 하는 예제이다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-web
spec:
selector:
matchLabels:
app: todo-web
template:
metadata:
labels:
app: todo-web
spec:
containers:
- name: web
image: kiamol/ch04-todo-list
volumeMounts:
- name: config
mountPath: "/app" # 이미지가 존재하는 경로이다.
readOnly: true
volumes:
- name: config
configMap:
name: todo-web-config-dev
# 설정에 오류가 있는 설정파일 적용
% kubectl apply -f todo-list/todo-web-dev-broken.yaml
deployment.apps/todo-web configured
# 웹애플리케이션이 잘 동작하는지 확인
# 잘 동작했다!
# 로그 확인.
# 두 개의 애플리케이션에서 출력하는 로그를 볼 수 있다.
# 하나는 설정에 오류난 애플리케이션, 다른 하나는 기존에 동작하던 애플리케이션
% kubectl logs -l app=todo-web
dbug: ToDoList.Pages.ConfigModel[0]
GET /config called
dbug: ToDoList.Pages.ConfigModel[0]
GET /config called
dbug: ToDoList.Pages.ConfigModel[0]
GET /config called
dbug: ToDoList.Pages.ConfigModel[0]
GET /config called
dbug: ToDoList.Pages.ConfigModel[0]
GET /config called
* You intended to execute a .NET application:
The application 'ToDoList.dll' does not exist.
* You intended to execute a .NET SDK command:
No .NET SDKs were found.
Download a .NET SDK:
https://aka.ms/dotnet-download
Learn about SDK resolution:
https://aka.ms/dotnet/sdk-not-found
# 파드 상태 확인
% kubectl get pods -l app=todo-web
NAME READY STATUS RESTARTS AGE
todo-web-559c9fb5f7-pdfpx 1/1 Running 1 (41m ago) 2d21h
todo-web-fd8cbdd6-bd7bb 0/1 CrashLoopBackOff 2 (23s ago) 48s
# 파드 상태 확인
wonjulee@wonjuui-MacBookAir ch04 % kubectl get pods -l app=todo-web
NAME READY STATUS RESTARTS AGE
todo-web-559c9fb5f7-pdfpx 1/1 Running 1 (43m ago) 2d21h
todo-web-fd8cbdd6-bd7bb 0/1 Error 5 (84s ago) 3m10s
위에서 볼 수 있듯이 파드의 개수가 2개가 되었다. 새로운 파드가 정상적으로 시작하지 않으면 기존 파드가 제거되지 않기 때문이다. 쿠버네티스는 계속해서 재시작을 시도하지만 우리의 설정파일은 잘못된 파일이므로
Error
상태로 변했다. 애플리케이션은 잘 동작하는 것처럼 보이지만, 설정파일이 적용되어 업데이트된 상태가 아닌 이전 상태임에 유의하자. 다시 올바르게 만들어보자.# 잘 동작하는 설정파일로 업데이트합니다.
% kubectl apply -f todo-list/todo-web-dev-no-logging.yaml
deployment.apps/todo-web configured
# 설정파일이 잘 들어갔는지 확인해봅니다.
% kubectl exec deploy/todo-web -- sh -c 'ls /app/config'
config.json
# 애플리케이션 로그 확인
% kubectl logs -l app=todo-web
# 파드 확인. 잘 동작하는 하나의 파드로 업데이트 되었습니다.
% kubectl get pods -l app=todo-web
NAME READY STATUS RESTARTS AGE
todo-web-85c6bc54b5-swfmc 1/1 Running 0 63s
비밀값을 이용해 민감한 정보 다루기
비밀값과 컨피그맵이 다른 점은 별도로 관리되어 노출이 최소화된다는 것이다. 비밀값은 필요한 노드에만 전달되며, 전달받은 노드에서는 디스크에는 저장되지 않고 메모리에만 담긴다. 그리고 전달할 때와 저장할 때 모두 암호화되어 처리된다. 다만, 비밀값 객체에 접근할 권한이 있는 경우에는 평문으로 읽을 수 있는데, base64로 인코딩된 형태이다.
명령행으로 비밀값 만들기
# 평문 리터럴로 비밀값 생성
% kubectl create secret generic sleep-secret-literal --from-literal=secret=test_secret_string
secret/sleep-secret-literal created
# 비밀값의 상세 정보 확인
% kubectl describe secret sleep-secret-literal
Name: sleep-secret-literal
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
secret: 18 bytes
# 비밀값 평문확인
% kubectl get secret sleep-secret-literal -o jsonpath='{.data.secret}'
dGVzdF9zZWNyZXRfc3RyaW5n
# 평문 디코딩
% kubectl get secret sleep-secret-literal -o jsonpath='{.data.secret}' | base64 -d
test_secret_string
base64 인코딩은 파드에 전달된 이후에는 디코딩되어, 평문이 담긴 텍스트 파일이 된다. 다음은 비밀값을 환경변수로 불러오는 애플리케이션 정의다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
env: # 환경변수 정의
- name: KIAMOL_SECRET # 컨테이너에 전달될 환경변수 이름
valueFrom: # 환경변수 값을 외부에서 가져오기
secretKeyRef: # 비밀값에서 가져오기
name: sleep-secret-literal # 비밀값 이름. 명령행으로 만들었다.
key: secret # 비밀값에서 항목 이름(key)
# 위 정의대로 디플로이먼트 업데이트
% kubectl apply -f sleep/sleep-with-secret.yaml
deployment.apps/sleep configured
# 파드 속 환경변수 확인
% kubectl exec deploy/sleep -- printenv KIAMOL_SECRET
test_secret_string
yaml로 비밀값 만들기
다음은 todo 애플리케이션의 DB를 다른 파드에서 동작하는 DB로 업데이트하는 것이다. DB는 PostgreSQL의 도커허브 이미지를 사용한다.
apiVersion: v1
kind: Secret # 비밀값 유형
metadata:
name: todo-db-secret-test # 비밀값 이름
type: Opaque # 텍스트 데이터를 담기 위해 Opaque 비밀값 유형
stringData: # 텍스트 데이터
POSTGRES_PASSWORD: "kiamol-2*2*" # 저장할 데이터 key-value
이 정의대로 비밀값을 생성하면
stringData
에 있는 데이터가 base64로 인코딩되어 저장된다. 비밀값을 yaml로 관리하는 것은 편리하기는 하지만 버전관리도구에 노출될 수 있으므로 주의가 필요하며, 운영환경에서는 yaml에 민감한 데이터가 포함되지 않도록 배포할 때 추가적인 처리를 해야한다. 또 일단 비밀값이 쿠버네티스 클러스터에 들어가게 되면, 권한이 있는 사람은 누구나 비밀값을 볼 수 있다는 사실도 인지하고 있어야한다.# 비밀값 생성하기
% kubectl apply -f todo-list/secrets/todo-db-secret-test.yaml
secret/todo-db-secret-test created
# 비밀값 데이터 확인하기. base64
% kubectl get secrets todo-db-secret-test -o jsonpath='{.data.POSTGRES_PASSWORD}'
a2lhbW9sLTIqMio=
# 비밀값 객체 어노테이션 확인. 평문
% kubectl get secrets todo-db-secret-test -o jsonpath='{.metadata.annotations}'
{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"todo-db-secret-test\",\"namespace\":\"default\"},\"stringData\":{\"POSTGRES_PASSWORD\":\"kiamol-2*2*\"},\"type\":\"Opaque\"}\n"}
파일로 전달하기
비밀값을 환경변수로 불러올 때는 주의해야한다. 환경변수는 컨테이너에서 동작하는 모든 프로세스에서 접근할 수 있고, 환경변수를 로그로 남기는 경우도 있기 때문이다. 좋은 방법 중 하나는 비밀값을 파일 형태로 전달하는 것이다.
POSTGRES_PASSWORD
이라는 데이터를 환경변수로 전달할 수 있지만 위와 같은 이유로 이상적인 방법은 아니다. 파일형태로 전달하기 위해서는 일단 비밀값을 파일 형태로 전달하고, 이 설정 파일의 경로(여기서는 POSTGRES_PASSWORD_FILE
) 를 환경변수로 지정하는 방법을 이용할 수 있다. 파일형태로 전달하는 방식을 이용하면 파일 권한을 볼륨에서 지정할 수 있다. 아래 정의를 보자.apiVersion: v1
kind: Service
metadata:
name: todo-db
spec:
ports:
- port: 5432
targetPort: 5432
selector:
app: todo-db
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-db
spec:
selector:
matchLabels:
app: todo-db
template:
metadata:
labels:
app: todo-db
spec:
containers:
- name: db
image: postgres:11.6-alpine
env:
- name: POSTGRES_PASSWORD_FILE # 설정파일의 경로를 환경변수로 지정.
value: /secrets/postgres_password
volumeMounts:
- name: secret # 마운트할 볼륨 이름
mountPath: "/secrets" # 마운트 되는 경로
volumes:
- name: secret
secret: # 비밀값에서 볼륨 생성
secretName: todo-db-secret-test # 볼륨을 만들 비밀값 이름
defaultMode: 0400 # 파일 권한 설정
items: # 비밀값의 특정 데이터 지정 가능
- key: POSTGRES_PASSWORD
path: postgres_password
이 파드를 배치하면 컨테이너의
/secrets/postgres_password
파일에 비밀값 데이터가 전달된다. 0400 권한을 통해 컨테이너 사용자만 읽을 수 있게 됐다. postgres 사용자는 환경변수로 읽을 수 있으므로 동일하게 이용할 수 있다.# 위 정의대로 파드를 생성합니다.
% kubectl apply -f todo-list/todo-db-test.yaml
service/todo-db created
deployment.apps/todo-db created
# 로그를 확인합니다.
% kubectl logs -l app=todo-db --tail 1
2024-06-22 15:28:42.661 UTC [1] LOG: database system is ready to accept connections
# 패스워드 설정 파일의 권한을 확인합니다.
# readlink는 파일의 실제 경로를 알려줍니다.
# 쿠버네티스에서 마운트 된 파일은 symlink를 통해 연결됩니다.
% kubectl exec deploy/todo-db -- sh -c 'ls -l $(readlink -f /secrets/postgres_password)'
-r-------- 1 root root 11 Jun 22 15:28 /secrets/..2024_06_22_15_28_28.2514918119/postgres_password
이제는 todo-list 애플리케이션이 바뀐 DB를 이용하도록 업데이트 해보자.
# PostgreSQL 데이터베이스를 이용하도록 컨피그맵을 업데이트합니다.
% kubectl apply -f todo-list/configMaps/todo-web-config-test.yaml
configmap/todo-web-config-test created
% cat todo-list/configMaps/todo-web-config-test.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-web-config-test
data:
config.json: |-
{
"ConfigController": {
"Enabled" : false
},
"Database" : {
"Provider" : "Postgres"
}
}
# 데이터베이스에 접속할 인증정보가 담긴 비밀값을 설정합니다.
% kubectl apply -f todo-list/secrets/todo-web-secret-test.yaml
secret/todo-web-secret-test created
% cat todo-list/secrets/todo-web-secret-test.yaml
apiVersion: v1
kind: Secret
metadata:
name: todo-web-secret-test
type: Opaque
stringData:
secrets.json: |-
{
"ConnectionStrings": {
"ToDoDb": "Server=todo-db;Database=todo;User Id=postgres;Password=kiamol-2*2*;"
}
}
# 변경된 설정을 읽도록 디플로이먼트와 서비스를 업데이트합니다.
% kubectl apply -f todo-list/todo-web-test.yaml
service/todo-web-test created
deployment.apps/todo-web-test created
% cat todo-list/todo-web-test.yaml
apiVersion: v1
kind: Service
metadata:
name: todo-web-test
spec:
ports:
- port: 8081
targetPort: 80
selector:
app: todo-web
environment: test
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-web-test
spec:
selector:
matchLabels:
app: todo-web
environment: test
template:
metadata:
labels:
app: todo-web
environment: test
spec:
containers:
- name: web
image: kiamol/ch04-todo-list
env:
- name: ASPNETCORE_ENVIRONMENT
value: Test
volumeMounts:
- name: config
mountPath: "/app/config"
readOnly: true
- name: secret
mountPath: "/app/secrets"
readOnly: true
volumes:
- name: config
configMap:
name: todo-web-config-test
items:
- key: config.json
path: config.json
- name: secret
secret:
secretName: todo-web-secret-test
defaultMode: 0400
items:
- key: secrets.json
path: secrets.json
# 비밀값이 잘 들어갔는지 확인합니다.
% kubectl exec deploy/todo-web-test -- cat /app/secrets/secrets.json
{
"ConnectionStrings": {
"ToDoDb": "Server=todo-db;Database=todo;User Id=postgres;Password=kiamol-2*2*;"
}
}
쿠버네티스 애플리케이션 설정 관리
애플리케이션에서 핵심적인 요구사항은 외부 환경에서 설정값을 주입받는 것이다. 설정값은 우선순위를 부여하여 환경변수나 파일의 형태로 주입되는 것이 이상적인데, 이를 위해 설계 단계에서 고려해야할 사항이 있다. 첫째는 ‘애플리케이션의 중단 없이 설정 변경에 대응이 필요한가?’ 두번째는 ‘민감정보를 어떻게 관리할 것인가’ 이다.
첫번째 사항을 만족하기 위해서는 제한되는 사항이 많은데 우선 환경변수를 이용할 수 없다. 파드 교체가 반드시 필요하기 때문이다. 따라서 볼륨 마운트로 설정 파일을 수정하는 방식을 써야하는데, 만약 볼륨 자체를 수정하는 경우에는 또 파드가 교체될 것이기 때문에, 기존 컨피그맵이나 비밀값을 업데이트 하는 방식으로 진행해야한다.
두번째 사항인 민감정보 관리를 위해서는 사내 설정관리 전담팀이 관리하거나 또는 yaml 템플릿 파일에는 민감정보가 채워질 곳을 빈칸으로 놓았다가, 배포할 때 Azure Keyvault 같이 안전한 곳에 보관되어있던 민감정보를 채워서 완성하는 방식을 이용할 수 있다. 중요한 것은 애플리케이션 플랫폼을 통해 설정값이 주입되어야 한다는 점이다. 그래야 환경과 무관하게 동일한 이미지를 사용할 수 있다.
정리하기
# 디렉토리 안에 있는 yaml 파일들에 정의된 모든 리소스를 삭제
% kubectl delete -f sleep/
deployment.apps "sleep" deleted
Error from server (NotFound): error when deleting "sleep/sleep-with-configMap-env.yaml": deployments.apps "sleep" not found
Error from server (NotFound): error when deleting "sleep/sleep-with-env.yaml": deployments.apps "sleep" not found
Error from server (NotFound): error when deleting "sleep/sleep-with-secret.yaml": deployments.apps "sleep" not found
Error from server (NotFound): error when deleting "sleep/sleep.yaml": deployments.apps "sleep" not found
% kubectl delete -f todo-list/
service "todo-db" deleted
deployment.apps "todo-db" deleted
deployment.apps "todo-web" deleted
service "todo-web-test" deleted
deployment.apps "todo-web-test" deleted
service "todo-web" deleted
Error from server (NotFound): error when deleting "todo-list/todo-web-dev-no-logging.yaml": deployments.apps "todo-web" not found
Error from server (NotFound): error when deleting "todo-list/todo-web-dev.yaml": deployments.apps "todo-web" not found
Error from server (NotFound): error when deleting "todo-list/todo-web.yaml": deployments.apps "todo-web" not found
% kubectl delete -f todo-list/configMaps
configmap "todo-web-config-dev" deleted
configmap "todo-web-config-test" deleted
Error from server (NotFound): error when deleting "todo-list/configMaps/todo-web-config-dev.yaml": configmaps "todo-web-config-dev" not found
% kubectl delete -f todo-list/secrets/
secret "todo-db-secret-test" deleted
secret "todo-web-secret-test" deleted
Error from server (NotFound): error when deleting "todo-list/secrets/todo-db-secret-test.yaml": secrets "todo-db-secret-test" not found
Share article