본문 바로가기
DevOps/Kubernetes

[11] 쿠버네티스 ConfigMap & Secret

by 비어원 2023. 8. 27.
728x90

보통 애플리케이션을 배포하면 하나의 환경에서만 배포하는 것이 아니라 여러 환경에서 배포를 하게 될 수도 있다. 그러면 환경마다 달라지는 것들이 있는데, 이런 것들을 보통 환경변수나 설정 파일 등으로 관리한다. 쿠버네티스에서는 이런 환경변수나 설정 파일 등을 저장하는 용도로 ConfigMap과 Secret을 제공해준다.

 

ConfigMap

ConfigMap은 Key-value 쌍의 형태로 데이터를 저장하는 데 사용하는 쿠버네티스 오브젝트이다. 보통 ConfigMap에 저장된 값을 파드에서 사용하는데, 파드의 환경변수에 ConfigMap의 값을 바인딩하거나 ConfigMap으로 파드 볼륨을 생성하여 볼륨 마운팅을 통해 파드 내부에서 파일의 형태로 ConfigMap을 사용할수도 있다.

 

선언 및 생성

ConfigMap은 metadatadata 부분으로 나눠져 있다. 다른 오브젝트와 다르게 spec은 없고 data만 있다. MySQL에서 사용할 데이터베이스 및 루트 비밀번호, 계정 등의 정보를 ConfigMap으로 나타내면 다음과 같다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: middleware
data:
  ROOT_PASWORD: root
  USER: beerone
  PASSWORD: beerone123
  DATABASE: TODO

물론 이와 같은 내용의 ConfigMap을 명령형으로도 생성할 수 있다.

$ kubectl create cm mysql -n middleware \
  --from-literal=ROOT_PASWORD=root \
  --from-literal=USER=beerone \
  --from-literal=PASSWORD=beerone123 \
  --from-literal=DATABASE=TODO

 

파드의 환경변수로 사용

MySQL에서 사용할 환경변수를 ConfigMap으로 생성한 후 실제로 MySQL 파드에 ConfigMap을 환경변수로 바인딩해보자. 다음과 같이 StatefulSet으로 MySQL 파드를 생성해보자.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
        env:
          - name: MYSQL_ROOT_PASSWORD
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: ROOT_PASSWORD 
                optional: false
          - name: MYSQL_USER
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: USER
          - name: MYSQL_PASSWORD
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: PASSWORD
          - name: MYSQL_DATABASE
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: DATABASE

        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
  • valueFrom.configMapKeyRef를 사용하여 .name이라는 ConfigMap의 .key 키에 해당하는 값을 특정 환경변수 값으로 바인딩 할 수 있다.

파드 생성 후 파드가 Running 상태에 있다면 exec 명령어로 mysql에 접속해보자. 비밀번호를 입력하면 mysql에 접속할 수 있을 것이다.

# password는 beerone123으로 설정했음
$ kubectl exec -it mysql-0 -- mysql -u beerone -p

Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.30 MySQL Community Server - GPL

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| TODO               |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
  • mysql에 접속 후 데이터베이스 항목을 조회하면 TODO 데이터베이스도 생성됨을 알 수 있다.

 

envFrom

Pod의 container에서 env 대신 envFrom을 사용하여 ConfigMap에 있는 key-value를 환경변수의 key-value로 모두 매핑할 수 있다. envFrom을 사용하면 ConfigMap에 선언된 key가 env의 key로, ConfigMap에 선언된 value가 env의 value로 매핑되기 때문에 ConfigMap의 key값을 사용할 환경변수의 key 값으로 맞춰줘야 한다.

mysql ConfigMap의 key 값을 아래와 같이 모두 변경하자.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: middleware
data:
  MYSQL_ROOT_PASSWORD: root
  MYSQL_USER: beerone
  MYSQL_PASSWORD: beerone123
  MYSQL_DATABASE: TODO

그 후 envFrom을 사용하여 StatefulSet을 생성해보자.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
        envFrom:
        - configMapRef:
            name: mysql
        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"

그 후 파드가 정상적으로 구동되었다면 exec 명령어로 환경변수가 잘 매핑되었는지 확인해보자.

$ kubectl exec -it mysql-0 env | grep MYSQL
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
MYSQL_MAJOR=8.0
MYSQL_VERSION=8.0.30-1.el8
MYSQL_SHELL_VERSION=8.0.30-1.el8
MYSQL_ROOT_PASSWORD=root
MYSQL_USER=beerone
MYSQL_PASSWORD=beerone123
MYSQL_DATABASE=TODO

 

파일로 볼륨 마운트하기

ConfigMap 안에 설정 파일의 정보를 저장한 후 파드에서 ConfigMap을 볼륨 마운팅해서 ConfigMap에 저장된 파일을 사용할 수도 있다.

 

일단 mysql의 설정파일을 ConfigMap에 저장해보자.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-conf
  namespace: middleware
data:
  my.cnf: |
    # For advice on how to change settings please see
    # http://dev.mysql.com/doc/refman/8.0/en/server-configuration-defaults.html

    [mysqld]
    #
    # Remove leading # and set to the amount of RAM for the most important data
    # cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
    # innodb_buffer_pool_size = 128M
    #
    # Remove leading # to turn on a very important data integrity option: logging
    # changes to the binary log between backups.
    # log_bin
    #
    # Remove leading # to set options mainly useful for reporting servers.
    # The server defaults are faster for transactions and fast SELECTs.
    # Adjust sizes as needed, experiment to find the optimal values.
    # join_buffer_size = 128M
    # sort_buffer_size = 2M
    # read_rnd_buffer_size = 2M

    # Remove leading # to revert to previous value for default_authentication_plugin,
    # this will increase compatibility with older clients. For background, see:
    # https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_default_authentication_plugin
    # default-authentication-plugin=mysql_native_password
    skip-host-cache
    skip-name-resolve
    datadir=/var/lib/mysql
    socket=/var/run/mysqld/mysqld.sock
    secure-file-priv=/var/lib/mysql-files
    user=mysql

    pid-file=/var/run/mysqld/mysqld.pid
    [client]
    socket=/var/run/mysqld/mysqld.sock

 

그 후 ConfigMap으로 파드의 볼륨을 생성한 후, 컨테이너에서 볼륨 마운트를 하면 된다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
        envFrom:
        - configMapRef:
            name: mysql
        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        volumeMounts:
          - name: config
            mountPath: /etc/mysql/conf.d
      volumes:
        - name: config
          configMap:
            name: mysql-conf

 

mountPath 하위에 configMap의 key들이 파일의 이름으로 마운트된다. exec 명령어로 설정파일이 마운팅 된 것을 확인할 수 있다.

$ kubectl exec mysql-0 ls /etc/mysql/conf.d
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
my.cnf

 

Secret

사실 ConfigMap 안에 패스워드 등 중요한 정보들을 넣는 것은 바람직하지 않다. ConfigMap은 데이터를 단순 평문으로 저장하기 때문이다. 그래서 쿠버네티스에서는에서는 패스워드 등 중요한 정보들을 저장하기 위해 Secret을 제공한다.

 

생성

일반적으로 Secret은 다음과 같이 명령어로 생성한다.

$ kubectl create secret generic mysql \
  --from-literal=MYSQL_ROOT_PASSWORD=root \
  --from-literal=MYSQL_PASSWORD=beerone123

시크릿은 다른 오브젝트와 다르게 명령어로 생성하는 것이 좋다. 이유를 보기 위해 생성된 시크릿을 yaml 형식으로 조회해보자.

$ kubectl get secret mysql -o yaml
apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: "2023-08-27T02:53:22Z"
  name: mysql
  namespace: middleware
  resourceVersion: "18436"
  uid: 1128b4df-c4b6-4fe6-96e0-770095c75ab5
type: Opaque
data:
  MYSQL_PASSWORD: YmVlcm9uZTEyMw==
  MYSQL_ROOT_PASSWORD: cm9vdA==

data의 value 부분이 평문이 아닌 Base64 인코딩이 된 채로 저장되는 것을 알 수 있다. 그래서 만약 yaml 파일을 생성하여 apply로 시크릿을 생성하려고 하면 다음과 같이 Base64 인코딩 한 채로 yaml파일을 생성해야 한다.

apiVersion: v1
kind: Secret
metadata:
  name: mysql
  namespace: middleware
type: Opaque
data:
  MYSQL_PASSWORD: YmVlcm9uZTEyMw==
  MYSQL_ROOT_PASSWORD: cm9vdA==

물론 stringData를 사용하여 평문으로 yaml 파일을 생성하여 시크릿을 생성할 수도 있다. 하지만 이는 바람직하지 않다. 시크릿 정보들이 파일로 노출이 되기 때문이다. 만약 Github등의 형상관리 시스템에서 yaml파일을 관리한다면 이 파일 자체가 형상관리 시스템에 저장되기 때문에, 여러 사람들이 이 시크릿 정보를 그냥 볼 수 있게 되어 보안에 취약해 질 수 있다.

apiVersion: v1
kind: Secret
metadata:
  name: mysql
  namespace: middleware
stringData:
  MYSQL_ROOT_PASSWORD: root
  MYSQL_PASSWORD: beerone123

당연히 data 로 시크릿을 yaml파일로 저장하는 것도 안된다. 기본적으로 시크릿은 Base64 인코딩이 된 채로 저장이 되는데, Base64는 누구나 디코딩할 수 있기 때문에 사실상 평문과 다를 바가 없다.

 

시크릿을 안전하게 사용하는 방법

Base64는 누구나 디코딩할 수 있기 때문에 시크릿이 안전한지 의문이 들 수도 있다. 그리고 공식 문서에서는 시크릿은 기본적으로 etcd에 암호화되지 않은 상태로 저장된다고 한다.

실제로 etcdctl을 사용하여 etcd에 시크릿이 어떻게 저장되어있는지 확인해보자.

# etcdctl 설치
$ sudo apt install etcd-client

# middleware 네임스페이스의 mysql 시크릿 조회
$ sudo ETCDCTL_API=3 etcdctl --endpoints=https://localhost:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  get /registry/secrets/middleware/mysql

/registry/secrets/middleware/mysql
k8s


v1Secret�
�
mysql
middleware"*$1128b4df-c4b6-4fe6-96e0-770095c75ab52����
kubectl-createUpdatev��FieldsV1:R
P{"f:data":{".":{},"f:MYSQL_PASSWORD":{},"f:MYSQL_ROOT_PASSWORD":{}},"f:type":{}}B
MYSQL_PASSWORD
beerone123
          MYSQL_ROOT_PASSWORDrootOpaque"
  • etcd에는 시크릿이 평문으로 저장되는 것을 확인할 수 있다.

그래서 기본적으로 API 접근 권한이 있거나 etcd 접근 권한이 있거나 특정 네임스페이스의 파드를 생성할 수 있는 권한이 있다면 누구든지 시크릿의 데이터를 읽을 수 있다. 그래서 시크릿을 안전하게 저장하려면 다음 방법을 고려하는 것을 권장한다.

  1. 시크릿에 대해 Encryption at Rest를 활성화한다.
  2. 시크릿에 대한 RBAC 권한 설정을 최소화 한다. (아무나 못보도록 권한 설정을 최소화 한다.)
  3. 특정 컨테이너에서만 시크릿에 접근하도록 한다.
  4. 외부 시크릿 저장소 제공 서비스를 사용한다.

4가지 방법 중 일부에 대해서 블로그 글을 쓸 계획이 있다.

 

파드의 환경변수로 사용

ConfigMap과 마찬가지로 Secret의 값을 파드의 환경변수로 바인딩 할 수 있다. 이전에 만들어뒀던 mysql ConfigMap에서 패스워드 정보만 Secret으로 옮기자.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: middleware
data:
  MYSQL_USER: beerone
  MYSQL_DATABASE: TODO
$ kubectl create secret generic mysql \
  --from-literal=MYSQL_ROOT_PASSWORD=root \
  --from-literal=MYSQL_PASSWORD=beerone123

 

그 후 패스워드와 관련된 환경변수들은 시크릿에서 환경변수를 바인딩하도록 configMapKeyRef가 아닌 secretKeyRef로 변경한 후 StatefulSet을 생성해보자.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
          - name: MYSQL_ROOT_PASSWORD
            valueFrom: 
              secretKeyRef:
                name: mysql
                key: MYSQL_ROOT_PASSWORD 
                optional: false
          - name: MYSQL_USER
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: MYSQL_USER
          - name: MYSQL_PASSWORD
            valueFrom: 
              secretKeyRef:
                name: mysql
                key: MYSQL_PASSWORD
          - name: MYSQL_DATABASE
            valueFrom: 
              configMapKeyRef:
                name: mysql
                key: MYSQL_DATABASE

        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"

 

envFrom

Secret도 ConfigMap과 마찬가지로 envFrom을 사용하여 시크릿에 저장된 모든 데이터들을 환경변수로 바인딩 할 수 있다. ConfigMap과도 같이 사용이 가능하다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
        envFrom:
          - configMapRef:
              name: mysql
          - secretRef:
              name: mysql
        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"

 

envFrom, env 우선순위

envFrom과 env의 우선순위를 알아보자. 우선 envFrom에서는 mysql ConfigMap과 mysql Secret에 저장된 key-value들을 모조리 파드의 환경변수로 가져온다. 그리고 env에서는 mysql Secret에 저장된 MYSQL_ROOT_PASSWORD를 다른 값으로 추가로 선언하였다. 그러면 최종적으로 env의 MYSQL_ROOT_PASSWORD과 환경변수로 저장될까, envFrom의 mysql Secret의 MYSQL_ROOT_PASSWORD에 해당하는 값이 환경변수로 저장될까?

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: middleware
spec:
  selector:
    matchLabels:
      app: mysql
      tier: middleware
  serviceName: "mysql"
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
        tier: middleware
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: mysql:8.0.30
        ports:
        - containerPort: 3306
          name: mysql
        env:
          - name: MYSQL_ROOT_PASSWORD
            value: root123
        envFrom:
          - configMapRef:
              name: mysql
          - secretRef:
              name: mysql
        readinessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -ec
              - |
                password_aux="${MYSQL_ROOT_PASSWORD:-}"
                mysqladmin status -uroot -p"${password_aux}"

정답은 직접 생성해보면 알겠지만 env가 우선순위가 더 높다.

$ kubectl exec mysql-0 -- env | grep MYSQL_ROOT_PASSWORD
MYSQL_ROOT_PASSWORD=root123
728x90

'DevOps > Kubernetes' 카테고리의 다른 글

Helm Chart 소개  (0) 2023.09.17
Encryption at Rest (쿠버네티스 데이터 암호화)  (0) 2023.08.27
[2] 쿠버네티스 클러스터 설치  (0) 2023.02.25
[10] 쿠버네티스 Ingress  (0) 2022.07.21
쿠버네티스 Static Pods  (0) 2022.07.16

댓글