Kubernetes ConfigMap & Secret, 제대로 이해하기

2024. 4. 16. 23:29BACKEND/Docker & Kubernetes

본 포스팅에서는 Kubernetes의 환경변수를 구성하는 방법인 ConfigMap & Secret 의 개념을 이해하고 적용할 수 있도록 하는 목표를 가집니다.

 

🔗  Kubernetes Series

모든 Kubernetes 시리즈를 확인하시려면 위를 참고해 주세요.

 

 


 

 

쿠버네티스를 사용하면서, 환경에 따라 다르거나 자주 변경되는 설정 옵션들을,

실행되는 애플리케이션 코드와 분리시키고 싶을 때가 생깁니다. 

환경 변수를 정의하면서 이를 해결할 수 있는데요.

 

본 포스팅에서는 쿠버네티스에서 환경변수를 설정할 수 있는

총 세가지의 방법을 소개해드리고자 합니다.

 

 

 

Environment Variables

🔗 Official: Configuration

 

 

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에 바로 입력하여 적용할 수 있습니다.

사용할 수 있는 옵션들에 대해 미리 살펴보면 아래와 같습니다.

 

kubernetes in action

 

 

$ 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

kubenetes - Secret

 

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