[Kubernetes] 컨피그맵(ConfigMap)과 시크릿(Secret)

설정 파일을 컨테이너 이미지 안에 포함하거나 파일이 포함된 볼륨을 컨테이너에 마운트 한다는 것은 설정 파일의 내용을 그 이미지에 접근할 수 있는 사람이라면 누구나 볼 수 있게 하는 것과 같다.

따라서 이미지에 설정 파일이나 설정 데이터를 저장하는 것은 바람직하지 않다. 그렇다면 설정 데이터를 컨테이너로 전달할 수 있는 방법이 필요한데, 컨피그맵과 시크릿을 이용하면 된다.

컨피그맵(ConfigMap)

컨피그맵은 설정 데이터를 저장하는 쿠버네티스 리소스로 key/value pair로 구성된 맵이다.

사실 컨피그맵은 보안상 민감한 데이터를 보관하기 보다는 상대적으로 자주 변경될 수 있는 설정 옵션을 일종의 "변수" 개념으로 보관하기 위한 것이다.

민감한 데이터는 추후 설명할 시크릿(secret)이라는 리소스에 저장하는 것이 바람직하다.

컨피그맵을 사용하는 방법은

  1. 환경변수로 컨테이너에 전달
  2. 컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출

2가지 방법이 있다.

컨피그맵 항목을 환경변수로 컨테이너에 전달

apiVersion: v1
kind: Pod
...
spec:
  containers:
  - image: ...
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval

위 설정파일에서 눈여겨 봐야할 부분은 valueFrom, configMapKeyRef 이 2개 항목이다. 컨피그맵에서 원하는 값을 찾아 환경변수로 주입할 때 valueFrom을 사용하며, 소스로 컨피그맵을 지정하기 위해 configMapKeyRef를 사용한다.

만약 파드를 생성할 때, 존재하지 않는 컨피그맵을 참조한다면 어떻게될까? 컨테이너는 시작하는 데 실패한다. 다만, 누락된 컨피그맵이 생성되고 나면 컨테이너는 파드를 다시 생성하지 않아도 자동으로 시작된다.

한편 env, valueFrom 대신 envFrom 속성을 사용하면 컨피그맵을 환경변수로 모두 노출할 수도 있다.

...
spec:
  containers:
  - image:...
    envFrom:
      configMapKeyRef
      ...

참고로 컨피그맵의 키가 올바른 형식이 아닌 경우 자동으로 건너뛰어진다. typo에 주의해야 한다.

그럼 이렇게 가져온 컨피그맵 항목을 어떻게 전달할까? 명령줄 인자로 전달할 수 있다.

...   configMapKeyRef:
        name:...
        key: ...
  args: ["$(변수명)"]

$(변수명) 문법으로 변수의 값을 인자로 주입할 수 있다.

컨피그맵 볼륨을 사용해 파일로 노출

다른 방법으로는 컨피그맵 볼륨도 있다.

파일로 컨피그맵의 각 항목을 노출할 수 있다. 컨테이너에서 실행 중인 프로세스는 이 파일의 내용을 읽고 필요한 데이터를 얻는다.

한편 볼륨을 마운트할 때 비어있지 않은 디렉터리에 마운트할 경우 마운트한 볼륨의 파일만 보여지고 기존 파일은 숨김처리되어 접근할 수 없어진다.

애플리케이션 재시작 없이 설정 없데이트

환경변수나 명령줄 인수로 설정 소스를 제공하면 프로세스가 실행되는 도중에 업데이트가 불가능하지만 컨피그맵 볼륨을 이용할 경우 프로세스 실행 중에도 컨테이너 재시작 없이 설정을 업데이트할 수 있다.

다만, 컨피그맵을 업데이트한 후 파일이 업데이트 되는 데 생각보다 긴 시간(길게는 1분)이 걸릴 수 있음을 유념해야 한다.

컨테이너 재시작 없이 컨피그맵 변경이 반영되게 하려면, 애플리케이션 측에 설정을 다시 읽는 기능을 지원해야 한다. nginx의 경우 reload를 해 새 설정을 불러오게 하는 방법이 있을 수 있다.

한편, 컨피그맵 볼륨에 있는 모든 파일은 한 번에 동시에 업데이트 되는데, 이는 심볼릭 링크를 사용해 업데이트된다. 따라서 설정 파일들의 버전이 서로 달라 충돌이 발생할 가능성이 없다.

앞서 설명했듯 컨테이너의 중요한 기능은 불변성(immutability)이므로 애플리케이션이 설정을 다시 읽는 기능을 지원하지 않는 경우 이미 존재하는 컨피그맵을 파드가 사용하는 동안 수정하지 말아야 한다.

또, 애플리케이션이 reloading을 지원하더라도 컨피그맵 볼륨의 파일이 실행 중인 모든 인스턴스에 걸쳐 동기적으로 업데이트 되진 않기 때문에 최대 1분 간 동기화되지 않은 상태로 있을 수 있다.

시크릿

시크릿은 컨피그맵과 유사하게 key/value pair로 구성되는 리소스로, 시크릿에 접근해야 하는 파드가 실행 중인 노드에만 개별 시크릿이 배포됨으로써 보안을 유지한다. 또, 각 노드는 시크릿을 디스크에 기록하지 않고 메모리에만 저장하는데, 물리 저장소는 시크릿을 삭제해도 디스크 완전 삭제(wiping) 작업이 필요하기 때문이다.

보안이 유지되야 하는 자격증명, 개인 암호화 키 등 민감 데이터는 시크릿을 이용해 제공해야 한다.

최신 버전의 쿠버네티스에서는 etcd가 시크릿을 암호화된 형태로 저장해 기존보다 더 안전하게 유지한다.

시크릿의 사용 역시

  1. 환경 변수로 컨테이너에 전달
  2. 시크릿 볼륨으로 노출

2가지 방법이 있다.

시크릿과 컨피그맵의 차이

시크릿은 컨피그맵과 본질적으로 유사하지만 몇가지 차이가 있다.

  • 시크릿 항목의 내용은 Base64 인코딩 문자열로 되어 있다. 따라서 바이너리 데이터도 담을 수 있다.
  • stringData필드를 통해 Base64 인코딩 없이 시크릿의 값을 지정할 수 있으나, 쓰기 전용이며 확인은 data 필드에서 Base64 인코딩된 값으로 확인(read)해야 한다.

그럼 언제 시크릿을, 언제 컨피그맵을 사용해야 할까?

  • 파드에서 시크릿을 읽을 때는 별도의 설정이 없어도 적절하게 디코딩되어 기록된다.
  • 민감하지 않은 일반 설정 데이터는 컨피그맵으로 관리한다
  • 민감한 데이터와 일반 데이터가 섞여있다면 시크릿으로 관리한다.
  • 시크릿의 최대 크기는 1MB로 제한되므로 가능하면 민감 데이터만 시크릿을 사용한다.

환경변수로 시크릿 노출

기존에 컨피그맵을 환경변수로 노출한 방법과 동일하되, configMapKeyRef 대신 secretKeyRef를 사용한다.

다만 애플리케이션은 오류 보고서에 환경변수를 기록하기도 하고, 로그에 환경변수를 남기기도 한다. 따라서 환경변수로 시크릿을 노출하는 것은 조심해야 한다. 안전을 위해서 시크릿 볼륨을 이용하자.

시크릿 볼륨으로 노출

컨피그맵과 거의 유사하다. 시크릿 볼륨을 생성하고 마운트해서 이용하면 된다.

앞서 말했듯이 시크릿 볼륨은 인메모리 파일시스템(tmpfs)을 사용해 시크릿 파일을 저장한다. 디스크 저장시, 시크릿을 완전히 제거하기 위해 wiping이 필요하기 때문이다.

기타: 프라이빗 도커 이미지 레지스트리

실제 프로덕션 환경에서는 프라이빗 이미지 레지스트리를 이용해 이미지를 관리할 것이다. 외부의 제 3자가 이미지를 다운받을 수 있으면 곤란하기 때문이다.

이런 경우 이미지를 가져오기 위해 쿠버네티스는 적절한 자격증명을 알아야 한다.

이번 장에서 배운 방식을 응용하면 시크릿을 이용해 쿠버네티스에 이미지 레지스트리에 대한 자격증명을 제공할 수 있다.

  1. 이미지 레지스트리 자격증명을 가진 시크릿 생성
  2. 파드 매니페스트 안에 해당 시크릿 참조

먼저 명령줄 기반으로 시크릿을 생성해보자.

kubectl create secret docker-registry mysecret --docker-username=name \
--docker-password=pw \
--docker-email=email@email.com

다음으로 파드 매니페스트에 시크릿을 추가해보자

apiVersion: v1
kind: Pod
metadata:
  name: private-img
spec:
  imagePullSecrets:
  - name: mysecret
  containers:
  - image: username/private:tag
    name: main

물론 매번 파드 매니페스트에 이렇게 작성해야만 프라이빗 이미지를 가져올 수 있는 것은 아니다.

서비스어카운으(ServiceAccount)에 시크릿을 추가해 모든 파드에 자동으로 추가되게 하는 법도 있다.