쿠버네티스 인 액션 - 컨피그맵과 시크릿 : 애플리케이션 설정 (6)

hyeokjin·2022년 9월 9일
0

kubernetes

목록 보기
6/9
post-thumbnail

지금까지 실습 과정에서 실행한 애플리케이션에는 어떠한 종류의 설정 데이터도 전달할 필요가 없었다.
거의 모든 애플리케이션은 빌드된 애플리케이션 자체에 포함하지 말아야 하는 설정(배포된 인스턴스별로 다른 세팅, 외부 시스템 액세스를 위한 자격증명 등)이 필요하다.
쿠버네티스에서 이런 애플리케이션을 실행할 때 설정 옵션을 어떻게 전달하는지 살펴보자.

컨테이너화된 애플리케이션 설정

새 애플리케이션 개발을 시작할 때 필요한 모든 설정을 애플리케이션에 포함하는 경우를 제외하면 일반적으로 명령줄 인수로 애플리케이션에 필요한 설정을 넘겨주는 것으로 시작한다.
예를 들어 MySql 공식 컨테이너 이미지에서는 루트 슈퍼 사용자 계정의 암호를 설정할 때 MYSQL_ROOT_PASSWORD 환경변수를 사용한다.

환경변수를 사용하는것이 왜 컨테이너에서 널리 사용될까? 도커 컨테이너 내부에 있는 설정 파일을 사용하는 것은 약간 까다롭다. 설정 파일을 컨테이너 이미지 안에 포함하거나 파일이 포함돼 있는 볼륨을 컨테이너에 마운트해야 하기 때문이다.
파일을 이미지 안에 넣고 빌드하는 것은 애플리케이션 소스 코드에 설정 내용을 넣고 하드코딩하는 것과 비슷하다. 그로 인해 인증 정보나 암호화 키와 같이 비밀로 유지해야 하는 내용을 포함하게 되면, 해당 이미지에 접근할 수 있는 사람이면 모두 볼수있기 때문이다

쿠버네티스는 설정 데이터를 최상위 레벨의 쿠버네티스 리소스에 저장하고 이를 기타 다른 리소스 정의와 마찬가지로 다른 파일 기반 스토리지에 저장 할 수 있다.
설정 데이터를 저장하는 쿠버네티스 리소스를 컨피그맵 이라고한다. 컨피그맵을 사용해 설정 데이터를 저장할지 여부에 관계없이 다음과 같은 방법으로 애플리케이션을 구성할 수 있다.

  • 컨테이너에 명령줄 인수 전달
  • 각 컨테이너를 위한 사용자 정의 환경변수 지정
  • 특수한 유형의 볼륨을 통해 설정 파일을 컨테이너에 마운트

보안 관점에서 보면 자격증명, 개인 암호화 키 등 민감한 보안을 유지해야하는 유사한 데이터들은 쿠버네티스 시크릿이라는 또 다른 유형의 중요한 오브젝트를 제공한다.
하나씩 살펴보도록 하겠다.

컨테이너에 명령줄 인자 전달

도커에서의 명령어와 인자 정의를 먼저 살펴보자.

Dockerfile에서 두개의 지침은 다음 두 부분을 정의한다.

  • ENTRYPOINT는 컨테이너가 시작될 때 호출될 명령어를 정의한다.
  • CMD는 ENTRYPOINT에 전달되는 인자를 정의한다.

ENTRYPOINT 명령어로 실행하고 기본 인자를 정의하려는 경우에만 CMD를 지정하는 것이다.
이미지를 추가 인자를 지정해 Dockerfile 안의 CMD에 정의된 값을 재정의한다.

$ docker run <image> <arguments>

shell과 exec 형식의 차이점

두 명령어는 두 가지 서로 다른 형식을 지원한다.

  • shell 형식 : ENTRYPOINT node app.js
  • exec 형식 : ENTRYPOINT ["node" "app.js"]

차이점은 내부에서 정의된 명령을 셸로 호출하는지 여부다.
exec 형식은 컨테이너 내부에서 node 프로세스를 직접 실행한다(셀 내부가아니다)
shell 형식은 shell에서 시작된다.

fortune 이미지에서 간격을 설정할 수 있도록 만들기

fortune 스크립트와 이미지에서 반복하는 주기를 변경할 수 있도록 수정해보자
INTERVAL 변수를 추가하고 첫 번째 명령줄 인자의 값으로 초기화한다.

fortuneloop.sh

#!/bin/bash
trap "exit" SIGINT
INTERVAL=$1
echo Configured to generate new fortune every $INTERVAL secondes
mkdir /var/htdocs

while :
do
  echo $(date) Writing fortune to /var/htdocs/index.html
  /usr/games/fortune > /var/htdocs/index.html
  sleep $INTERVAL
done

그리고 Dockerfile을 수정해 exec 버전 ENTRYPOINT 명령을 사용하도록 하고 기본 간격으로 10초를 CMD 명령으로 지정한다.

Dockerfile

FROM ubuntu:latest

RUN apt-get update ; apt-get -y install fortune
ADD fortuneloop.sh /bin/fortuneloop.sh

ENTRYPOINT ["/bin/fortuneloop.sh"]	# exec 형태의 ENTRYPOINT 명령
CMD ["10"]		# 실행할 때 사용할 기본 인자

이제 이미지를 빌드하고 도커 허브에 푸시할 수 있다. 이번에는 이미지 태그를 lastest 대신 args로 지정한다.

$ docker build -t docker.io/luksa/fortune:args .
$ docker push docker.io/luksa/fortune:args

로컬에서 도커로 이미지를 실행해 테스트할 수 있다.

$ docker run -it docker.io/luksa/fortune:args

또한 기본 sleep 시간 간격을 인자로 전달해 재정의할 수 있다.

$ docker run -it docker.io/luksa/fortune:args 15

쿠버네티스에서 명령과 인자 재정의

쿠버네티스에서 컨테이너를 정의할 때, ENTRYPOINT와 CMD 둘 다 재정의할 수 있다. 그러기 위해 다음과 같이 컨테이너 정의 안에 command와 args 속성을 지정한다.


kind: Pod
spec:
  containers:
  - image: some/image
    command: ["/bin/command"]
	arg: ["arg1", "arg2"]

도커의 ENTRYPOINT는 쿠버네티스의 command로 사용하며 컨테이너 안에서 실행되는 파일을 의미하고
도커의 CMD는 쿠버네티스의 args로 사용하며 실행파일에 전달되는 인자를 뜻한다.

사용자 정의 주기로 fortune 파드 실행

fortune-pod-args.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune2s		# 파드 이름 변경 
spec:
  containers:
  - image: luksa/fortune:args	# 이미지 변경, 태그가 args인 것을 사용
    args: ["2"]					# 스크립트가 2초마다 새로운 fortune 메시지를 생성하도록 인자 지정
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

인자를 지정하는 것은 명령줄 인자로 설정 옵션을 컨테이너에 전달하는 한 방법이다.
다음으로 환경변수를 이용하는 방법을 살펴보자.

컨테이너의 환경변수 설정

앞선 예제처럼, 컨테이너화된 애플리케이션은 종종 환경변수를 설정 옵션의 소스로 사용한다.
쿠버네티스는 파드의 각 컨테이너를 위한 환경변수리스트를 지정할 수 있다.

환경변수로 fortune 이미지 안에 간격을 설정할 수 있도록 만들기

fortuneloop.sh 스크립트를 다음 예제처럼 수정해서 환경변수로 애플리케이션을 설정하는 방법을 살펴보자.

fortuneloop.sh

#!/bin/bash
trap "exit" SIGINT

echo Configured to generate new fortune every $INTERVAL seconds

mkdir -p /var/htdocs

while :
do
  echo $(date) Writing fortune to /var/htdocs/index.html
  /usr/games/fortune > /var/htdocs/index.html
  sleep $INTERVAL
done

기존 생성했던 코드에서 INTERVAL 변수를 초기화하는 행을 제거했다.
파드의 애플리케이션은 간단한 bash 스크립트이기 때문에 다른 작업을 할 필요가 없다.

컨테이너 정의에 환경변수 지정

새로운 이미지(luksa/fortune:env 태그를 붙인 이미지)를 생성한 뒤에 도커허브로 푸시한 후
새 파드를 만들 때 환경변수를 컨테이너 정의에 포함해 스크립트에 전달할 수 있다.

fortune-pod-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-env
spec:
  containers:
  - image: luksa/fortune:env
    env:				# 환경변수 목록에 단일 변수 추가 
    - name: INTERVAL	
      value: "30"
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

환경변수는 파드 레벨이 아닌 컨테이너 정의 안에 설정한 것을 볼 수 있다.

변숫값에서 다른 환경변수 참조

이전 예에서는 환경변수에 고정 값을 설정했지만 $() 구문을 사용하여 기존 변수를 참조할 수도 있다.

env:
- name: FIRST_VAR
  vaule: "foo"
- name: SECOND_VAR
  value: "$(FIRST_VAR)bar"

이 경우 SECOND_VAR의 값은 "foobar"가 된다.

컨피그맵으로 설정 분리

애플리케이션 구성의 요점은 환경에 따라 다르거나 자주 변경되는 설정 옵션을 애플리케이션 소스 코드와 별도로 유지하는 것이다.
쿠버네티스에서는 설정 옵션을 컨피그맵이라 부르는 별도 오브젝트로 분리할 수 있다. 컨피그맵은 짧은 문자열에서 전체 설정 파일에 이르는 값을 가지는 키/값 쌍으로 구성된 맵이다.

애플리케이션은 컨피그맵을 직접 읽거나 심지어 존재하는 것을 몰라도 된다. 대신 맵의 내용은 컨테이너의 환경변수 또는 볼륨파일로 전달된다.
또한 환경변수는 $() 구문을 사용해 명령줄 인수에서 참조할 수 있기 때문에, 컨피그맵 항목을 프로세스의 명령줄 인자로 전달할 수도 있다.
파드는 컨피그맵 이름으로 참조하기 때문에 모든 환경에서 동일한 파드 정의를 사용해 각 환경에서 서로 다른 설정을 사용할 수 있다.

컨피그맵 설정

앞에서 만든 파드에 컨피그맵을 사용하는 방법을 알아보자.
간단한 예제로, 단일 키를 가진 맵을 생성하고 이를 사용해 이전 예제의 INTERVAL 환경변수를 채워 넣자.

kubectl create -f 명령어로 YAML 파일을 게시하는 대신에 kubectl create configmap 명령으로 컨피그맵을 생성할 수 있다.

먼저 간단한 문자열을 이용해보자.

$ kubectl create configmap fortune-config --from-literal=sleep-interval=25
configmap/fortune-config created

해당명령어는 sleep-interval=25 라는 단일 항목을 가진 fortune-config 컨피그맵을 생성한다.
(만약 여러 문자열 항목을 가진 컨피그맵을 생성하려면 여러 개의 --from-literal 인자를 추가하면 된다)

생성한 컨피그맵을 kubectl get 명령으로 YAML 정의를 출력해보자

$ kubectl get configmap fortune-config -o yaml
apiVersion: v1
data:
  sleep-interval: "25"	# 이 맵의 단일 항목
kind: ConfigMap			# 이 디스크립터는 컨피그 맵을 설명한다.
metadata:
  creationTimestamp: "2022-09-09T14:19:17Z"	
  name: fortune-config	# 이 맵의 이름(해당 이름으로 참조)
  namespace: default
  resourceVersion: "3129675"
  uid: 5c39799c-9aa1-41d6-9957-5b24e891c5fe

특별한 것 없다. 이 YAML 파일을 쉽게 작성할 수 있다. 그리고 이 파일을 쿠버네티스 API에 게시한다. (kubectl create configmap 명령으로 이미 생성했다)

$ kubectl create -f fortune-config.yaml

파일 내용으로 컨피그맵 생성

컨피그맵에서는 전체 설정 파일 같은 데이터를 통째로 저장하는 것도 가능하다. kubectl create configmap 명령을 이용해 파일을 디스크에서 읽어 개별 항목으로 저장할 수 있다.

$ kubectl create configmap my-config --from-file=config-file.conf

kubectl을 실행한 디렉터리에서 config-file.conf 파일을 찾는다 그리고 파일 내용을 컨피그맵의 config-file.conf 키 값으로 저장한다.

물론 키 이름을 직접 지정할 수도있다.

$ kubectl create configmap my-config --from-file=customkey=config-file.conf

이 명령은 파일 내용을 customkey라는 키 값으로 저장한다. 문자열과 마찬가지로 --from-file 인수를 여러 번 사용해 여러 파일을 추가할 수 있다.

디렉터리에 있는 파일로 컨피그맵 생성

각 파일을 개별적으로 추가하는 대신, 디랙터리 안에 있는 모든 파일을 가져올 수도 있다.

$ kubectl create configmap my-config --from-file=/path/to/dir

이 명령에서 kubectl은 지정한 디렉터리 안에 있는 각 파일을 개별 항목으로 작성한다.
이때 파일 이름이 컨피그맵 키로 사용하기에 유효한 파일만 추가된다.

다양한 옵션 결합

컨피그맵을 생성할 때 여기에서 언급한 모든 옵션을 조합해 사용할 수 있다.

$ kubectl create configmap my-config
--from-file=foo.json		# 단일 파일
--from-file=bar=foobar.conf	# 사용자 정의 키 밑에 파일 저장
--from-file=config-opts/	# 전체 디렉터리
--from-file=some=thing	# 문자열 값

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

이렇게 생성한 맵의 값을 어떻게 파드 안의 컨테이너로 전달할 수 있을까?
세 가지 옵션이 있다. 가장 간단한 환경변수를 설정하는 것부터 시작하자.

fortune-pod-env-configmap.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-env-from-configmap
spec:
  containers:
  - image: luksa/fortune:env
    env:				# INTERVAL 환경변수를 설정하는 중
    - name: INTERVAL
      valueFrom: 		# 고정 값을 설정하는 대신 컨피그맵 키에서 값을 가져와 초기화한다.
        configMapKeyRef:
          name: fortune-config	# 참조하는 컨피그맵 이름
          key: sleep-interval	# 컨피그맵에서 해당 키 아래에 저장된 값으로 변수 설정
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

INTERVAL 환경변수를 선언하고 fortune-config 컨피그맵에 안에 있는 sleep-interval 키를 이용해 가져온 값으로 설정했다.
html-generator 컨테이너 안에서 실행 중인 프로세스는 INTERVAL 환경변수를 읽을 때 25를 가져온다.

파드에 존재하지 않는 컨피그맵 참조

파드를 생성할 때 존재하지 않는 컨피그맵을 지정하고 시작하면 해당 컨테이너는 실패한다.
하지만 참조하지 않는 다른 컨테이너는 정상적으로 시작된다. 그런 다음 누락된 컨피그맵을 생성하면 실패했던 컨테이너는 파드를 다시 만들지 않아도 시작된다.

컨피그맵의 모든 항목을 한 번에 환경변수로 전달

쿠버네티스 버전 1.6 부터는 컨피그맵의 모든 항목을 환경변수로 노출하는 방법을 제공한다. FOO, BAR, FOO-BAR 라는 세 개의 키를 갖고 있는 컨피그맵을 생각해보자. 이전 예제에서 env 속성 대신 envFrom 속성을 사용해 환경변수로 모두 노출할 수 있다.

spec:
  containers:
  - image: some-image
  envForm:			# env 대신 envFrom 사용
  - prefix: CONFIG_ 	# 모든 환경변수는 CONFIG_ 접두사를 가짐
  	  configMapRef:		# my-config-map 이란 이름의 컨피그맵 참조
        name: my-config-map

환경변수 앞에 붙을 접두사를 지정할 수 있다. 결과적으로 이 컨테이너 안에는 두 개의 환경변수가 존재한다. CONFIG_FOO와 CONFIG_BAR다.

하지만 FOO-BAR 항목의 경우 대시(-)를 포함하고 있어 올바른 환경변수 이름이 아니기 때문에 이런 경우에 쿠버네티스는 어떤 형태로든 임의로 키로 변환하지 않는다.

컨피그맵 항목을 명령줄 인자로 전달

이제 컨피그맵 값을 컨테이너 안에서 실행되는 프로세스의 인자로 전달하는 방법을 살펴보자.

fortune-pod-args-configmap.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-args-from-configmap
spec:
  containers:
  - image: luksa/fortune:args	# 환경변수가 아닌 첫번째 인자에서 간격을 가져오는 이미지 사용
    env:			# 컨피그맵에서 환경변수 정의
    - name: INTERVAL
      valueFrom: 
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    args: ["$(INTERVAL)"]	# 인자에 앞에서 정의한 환경변수 지정
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

앞에서 한 것과 동일하게 환경변수를 정의했지만 $(ENVVARIABLENAME) 문법을 사용해 쿠버네티스가 해당 변수의 값을 인자에 주입한다.

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

컨피그맵은 모든 설정 파일을 포함할 수 있다. 특수 볼륨 유형 중 하나인 컨피그맵 볼륨을 사용해보자.

fortuneloop.sh 스크립트를 수정하는 대신 다른 예를 시도해보자. fortune 파드의 웹 서버 컨테이너 안에서 실행되는 Nginx 웹 서버의 환경 설정을 위해 설정 파일을 사용할 것이다. Nginx 서버가 클라이언트로 응답을 압축해서 보내려고 한다고 가정해보자.

Nginx 설정 파일에 다음과 같은 내용이 포함되야한다.

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;
    }

}

이제 kubectl delete configmap fortune-config 명령으로 기존 fortune-config 컨피그맵을 삭제하고 Nginx 설정 파일을 포함하는 새로운 컨피그맵으로 교체할 수 있다. 로컬 디스크에 저장된 파일을 이용해 컨피그맵을 생성하자.

configmap-files라는 새 디렉터리를 생성하고 앞 예제 Nginx 설정 파일을 configmap-files/my-nginx-config.conf 파일로 저장한다.
컨피그맵에 sleep-interval 항목도 포함시키려면, 동일한 디렉터리에 sleep-interval이라는 일반 텍스트 파일을 생성하고 25를 저장한다.

mkdir configmap-files
cd configmap-files
vi my-nginx-config.conf 
vi sleep-interval
$ kubectl create configmap fortune-config --from-file=configmap-files
configmap/fortune-config created

다음은 YAML 정의를 보여준다

$ kubectl get configmap fortune-config -o yaml
apiVersion: v1
data:
  my-nginx-config.conf: |			# Nginx 설정 파일 내용을 담고 있는 항목
    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: |			# sleep-interval 항목
    25
kind: ConfigMap
metadata:
  creationTimestamp: "2022-09-09T14:42:30Z"
  name: fortune-config
  namespace: default
  resourceVersion: "3139072"
  uid: 6989936d-f057-4aa9-b430-67a9e1edbac4

컨피그맵은 두 항목을 포함하며 각 키는 해당 항목을 생성한 파일 이름으로 돼 있다. 이제 이 컨피그맵을 파드의 두 컨테이너에서 사용하자.

볼륨 안에 있는 컨피그맵 항목 사용

Nginx는 /etc/nginx/nginx.conf 파일의 설정을 읽는다. Nginx 이미지는 기본 설정 옵션을 가진 파일을 이미 포함하며, 이 파일이 가진 기본 옵션을 모두 무시하고 싶지는 않다. 다행히 기본 설정 파일을 /etc/nginx/conf.d/ 디렉터리 안에 있는 모든 .conf 파일을 포함하기 때문에 원하는 설정 파일을 해당 디렉터리에 추가하면 된다.

컨피그맵 항목을 파일로 마운트한 파드다.

fortune-pod-configmap-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-configmap-volume
spec:
  containers:
  - image: luksa/fortune:env
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d		# 컨피그맵 볼륨을 마운트하는 위치
      readOnly: true
    - name: config
      mountPath: /tmp/whole-fortune-config-volume
      readOnly: true
    ports:
      - containerPort: 80
        name: http
        protocol: TCP
  volumes:
  - name: html
    emptyDir: {}
  - name: config
    configMap:			# 이 볼륨은 fortune-config 컨피그맵을 참조한다.
      name: fortune-config

이 파드 정의에는 fortune-config 컨피그맵을 참조하는 볼륨이 포함돼 있다. 해당 볼륨을 Nginx에서 사용할 수 있도록 /etc/nginx/conf.d 디렉터리로 마운트한다.

이제 웹 서버는 응답을 압축해서 보내주도록 설정돼 있어야 한다. 이를 검증하려면 localhost:8080을 파드의 80번 포트로 전달하도록 연결하고, curl 명령을 이용해 서버 응답을 확인할 수 있다.

$ kubectl create -f fortune-pod-configmap-volume.yaml
pod/fortune-configmap-volume created

$ kubectl port-forward fortune-configmap-volume 8080:80 &
Forwarding from 127.0.0.1:8080 -> 80

$ curl -H "Accept-Econding: gzip" -I localhost:8080
Handling connection for 8080
HTTP/1.1 200 OK
Server: nginx/1.23.1
Date: Fri, 09 Sep 2022 14:46:18 GMT
Content-Type: text/html
Content-Length: 71
Last-Modified: Fri, 09 Sep 2022 14:45:55 GMT
Connection: keep-alive
ETag: "631b51a3-47"
Accept-Ranges: bytes
Content-Encoding: gzip	# 응답이 압축됐음을 나타냄

응답을 통해 원하는 것을 달성했음을 확인할 수 있다. 이제 /etc/nginx/conf.d 디렉터리에 무엇이 있는지 살펴보자

$ kubectl exec fortune-configmap-volume -c web-server ls /etc/nginx/conf.d
my-nginx-config.conf
sleep-interval

컨피그맵의 두 항목이 모두 디렉터리에 파일로 추가돼 있다. 서로 다른 두 개의 컨피그맵을 작성해 하나는 fortuneloop 컨테이너에 사용하고, 나머지 하나는 web-server 컨테이너에 사용하도록 할 수 있다.

볼륨에 특정 컨피그맵 항목 노출

다행히 컨피그맵 볼륨을 컨피그맵 항목의 일부만으로 채울 수 있다.
컨피스맵 볼륨 안에 파일로 노출될 항목을 정의하려면 다음 예제의 볼륨의 items 속성을 사용한다.

fortune-pod-configmap-volume-with-items.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-configmap-volume-with-items
spec:
  containers:
  - image: luksa/fortune:env
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d/
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}
  - name: config
    configMap:
      name: fortune-config
      items:			# 볼륨에 포함할 항목을 조회해 선택
      - key: my-nginx-config.conf	# 해당 키 아래에 항목포함
        path: gzip.conf		# 항목 값이 지정된 파일에 저장

/etc/nginx/conf.d 디렉터리는 gzip.conf 파일만 포함하고, 그 밖에 다른 것은 포함하지 않는다

디렉터리를 마운트할 때 디렉터리의 기존 파일을 숨기는 것 이해

이 예제와 이전 예제에서 볼륨을 디렉터리에 마운트 했다. 이는 컨테이너 이미지 자체에 있던 /etc/nginx/conf.d 디렉터리 안에 저장된 파일을 숨겼음을 의미한다. 이는 일반적으로 리눅스에서 파일시스템을 비어 있지 않은 디렉터리에 마운트할 때 발생한다. 해당 디렉터리는 마운트한 파일시스템에 있는 파일만 포함하고, 원래 있던 파일은 해당 파일시스템이 마운트돼 있는 동안 접근할 수 없게 된다.

일반적으로 중요한 파일을 포함하는 /etc 디렉터리에 볼륨을 마운트한다면 모든 원본 파일이 더 이상 존재하지 않기 때문에 전체 컨테이너가 손상될 수 있다. 만약 /etc 디렉터리와 같은 곳에 파일을 추가하는 것이 필요하다면, 이 방법을 사용할 수 없다.

디렉터리 안에 다른 파일을 숨기지 않고 개별 컨피그맵 항목을 파일로 마운트

컨피그맵의 항목을 개별 파일로 기존 디렉터리 안에 있는 모든 파일을 숨기지 않고 추가하는 방법이 궁금할 것이다.
전체 볼륨을 마운트하는 대신 volumMount에 subPath 속성으로 파일이나 디렉터리 하나를 볼륨에 마운트 할 수 있다.

spec:
  containers:
  - image: some-image
  volumMounts:
  - name: myvolume
    mountPath: /etc/someconfig.conf		# 디렉터리가 아닌 파일을 마운트
    subPath: myconfig.conf		# 전체 볼륨을 마운트하는 대신 myconfig.conf 항목만 마운트

subPath 속성은 모든 종류의 볼륨을 마운트할 때 사용할 수 있다. 전체 볼륨을 마운트 하는 대신에 일부만을 마운트할 수있다.

컨피그맵 볼륨 안에 있는 파일 권한 설정

기본적으로 컨피그맵 볼륨의 모든 파일 권한은 644 로 설정된다.
볼륨 정의 안에 있는 defaultMode 속성을 설정해 변경할 수 있다.

fortune-pod-configmap-volume-defaultMode.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-configmap-volume
spec:
  containers:
  - image: luksa/fortune:env
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
    - name: config
      mountPath: /tmp/whole-fortune-config-volume
      readOnly: true
  volumes:
  - name: html
    emptyDir: {}
  - name: config
    configMap:
      name: fortune-config
      defaultMode: 0660		# 모든 파일 권한을 -rw-rw-----로 설정

컨피그맵은 중요하지 않은 설정 데이터만 사용해야 하지만 이전 예제처럼 파일을 소유한 사용자와 그룹만 파일을 읽고 쓸 수 있도록 만들 수 있다.

애플리케이션을 재시작하지 않고 애플리케이션 설정 업데이트

환경변수 또는 명령줄 인수를 설정 소스로 사용할 때의 단점은 프로세스가 실행되고 있는 동안에 업데이트할 수 없다는 것이다. 컨피그맵을 사용해 볼륨으로 노출하면 파드를 다시 만들거나 컨테이너를 다시 시작할 필요 없이 설정을 업데이트 할 수 있다. 컨피그맵을 업데이트하면, 이를 참조하는 모든 볼륨의 파일이 업데이트 된다.

시크릿으로 민감한 데이터를 컨테이너 전달

이 부분에서는 정보의 보안을 유지할 필요가 있는 데이터로 자격증명과 개인 암호화 키 같은 민감한 정보가 포함되어있는 데이터를 말한다.
이러한 정보를 보관하고 배포하기 위해 쿠버네티스는 시크릿이라는 별도 오브젝트를 제공한다. 시크릿은 컨피그맵과 유사하며 다음과 같은 상황에서 사용할 수 있다.

  • 환경변수로 시크릿 항목을 컨테이너에 전달
  • 시크릿 항목을 볼륨 파일로 노출

기본 토큰 시크릿 소개

모든 파드에는 secret 볼륨이 자동으로 연결돼 있다. 이전 kubectl describe 명령어의 출력은 default-token-4x5pw이라는 시크릿을 참조한다. 시크릿은 리소스이기 때문에 kubectl get secrets 명령어로 목록을 조회하고 거기서 default-token 시크릿을 찾을수 있다.

$ kubectl get secrets
NAME                  TYPE                                  DATA   AGE
default-token-4x5pw   kubernetes.io/service-account-token   3      5d9h
$ kubectl describe secrets
Name:         default-token-4x5pw
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: default
              kubernetes.io/service-account.uid: 83d6b10c-9889-4962-837a-fb1bd9699b27

Type:  kubernetes.io/service-account-token

Data		# 이 시크릿은 세 가지 항목을 갖고 있다
====
ca.crt:     1509 bytes
namespace:  7 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6Im41SFZVcXlITnAwbFBoUVNUVFlJN0hPMWZSVDVzRXpJSmRCRXdBM2V5OWMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZX...

시크릿이 가지고 있는 세 항목은 파드 안에서 쿠버테스트 API 서버와 통신할 때 필요한 모든 것을 나타낸다. (ca.crt, namespace, token)

시크릿은 컨피그맵과 비슷하기 때문에 secret 볼륨이 마운트된 디렉터리에서 세 개의 파일을 볼 수 있을 것이라 예상할 수 있다.

$ kubectl exec [mypod] ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt
namespace
token

이 세 가지 파일을 사용해 API 서버에 접근하는 방법을 살펴보자

시크릿 생성

fortune-serving Nginx 컨테이너가 HTTPS 트래픽을 제공할 수 있도록 개선해보자. 이를 위해 인증서와 개인 키를 만들어야 한다. 개인 키는 안전하게 유지해야 하므로 개인 키와 인증서를 시크릿에 넣자.

$ openssl genrsa -out https.key 2048
$ openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com

시크릿에 대해 몇 가지 사항을 잘 설명하기 위해 foo 라는 추가 더미파일을 만들고 그 안에 bar라는 문자열을 저장하자.

$ echo bar > foo

이제 secert 명령으로 세 가지 파일에서 시크릿을 만들 수 있다.

$ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo

fortune-https 이름을 가진 generic 시크릿을 생성했다.
--from-file 옵션에서 개별 파일을 지정할 수 있지만 대신 디렉터리 전체를 포함할 수 도 있다.

컨피그맵과 시크릿 비교

시크릿과 컨피그맵은 큰 차이가 있다.

secret/fortune-https 정보

$ kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
  foo: YmFyCg==
  https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lVS3lZeG...
  https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc2NGazJ...
kind: Secret
metadata:
  creationTimestamp: "2022-09-09T14:58:45Z"
  name: fortune-https
  namespace: default
  resourceVersion: "3145692"
  uid: 81f24815-1dfd-48f8-8355-92e519dcd99c
type: Opaque

configmap/fortune-config 정보

$ 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: |
    25
kind: ConfigMap
metadata:
  creationTimestamp: "2022-09-09T14:42:30Z"
  name: fortune-config
  namespace: default
  resourceVersion: "3139072"
  uid: 6989936d-f057-4aa9-b430-67a9e1edbac4

시크릿 항목의 내용은 Base64 인코딩 문자열로 표시되고, 컨피그맵의 내용은 일반 텍스트로 표시된다. 시크릿은 각 항목을 설정하고 읽을 때마다 인코딩과 디코딩을 해야 한다.

시크릿 항목에 일반 텍스트뿐만 아니라 바이너리 값도 담을 수 있다. Base64 인코딩은 바이너리 데이터를 일반 텍스트 형식인 YAML이나 JSON 안에 넣을 수 있다.

파드에서 시크릿 사용

인증서와 키 파일을 모두 포함하는 fortune-https 시크릿을 Nginx에서 사용할 수 있도록 설정하는 것이 필요하다.

HTTPS를 활성화 하기위해 fortune-config 컨피그맵 설정 파일을 다시 수정한다.

$ kubectl edit configmap fortune-config

...
server {
    listen              80;
    listen              443 ssl;
    server_name         www.kubia-example.com;
    ssl_certificate     certs/https.cert;		# 각 경로는 /etc/nginx를 기준으로 지정
    ssl_certificate_key certs/https.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
...
}

설정에서 서버가 인증서와 키 파일을 /etc/nginx/certs 경로에서 읽도록 지정했기 때문에 secret 볼륨을 해당 위치에 마운트하는 것이 필요하다.

다음으로 새로운 fortune-https 파드를 만들고 인증서와 키를 가지고 있는 secret 볼륨을 web-server 컨테이너 안에 적당한 위치에 마운트 한다.

fortune-pod-https.yaml

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
  - image: luksa/fortune:env
    name: html-generator
    env:
    - name: INTERVAL
      valueFrom: 
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
    - name: certs		# Nginx 서버가 인증서와 키를 /etc/nginx/certs에서 읽도록 설정했기 때문에 시크릿 볼륨을 해당 위치에 마운트한다.
      mountPath: /etc/nginx/certs/
      readOnly: true
    ports:
    - containerPort: 80
    - containerPort: 443
  volumes:
  - name: html
    emptyDir: {}
  - name: config
    configMap:
      name: fortune-config
      items:
      - key: my-nginx-config.conf
        path: https.conf
  - name: certs		#  fortune-https 시크릿을 참조하도록 시크릿 볼륨을 정의한다.
    secret:
      secretName: fortune-https

파드가 실행되면 포트 포워드 터널링으로 파드의 443번 포트로 열고 curl 명령으로 요청을 보내 HTTPS 트래픽을 제공하는지 확인할 수 있다.

$ kubectl create -f fortune-pod-https.yaml
pod/fortune-https created

$ kubectl port-forward fortune-https 8443:433 &
Forwarding from 127.0.0.1:8443 -> 433

$ curl https://localhost:8443 -k -v
*   Trying 127.0.0.1:8443...
* Handling connection for 8443
Connected to localhost (127.0.0.1) port 8443 (#0)
...

-v 옵션은 상세로깅을 볼 수 있다.

시크릿 볼륨을 메모리에 저장하는 이유

인증서와 개인 키를 secret 볼륨에 마운트해 파드가 성공적으로 전달했다.
secret 볼륨은 시크릿 파일을 저장하는 데 인메모리 파일시스템을 사용한다. 컨테이너에 마운트된 볼륨을 조회하면 이를 볼 수 있다.

$ kubectl exec fortune-https -c web-server -- mount | grep certs
tmpfs on /etc/nginx/certs type tmpfs (ro,relatime,size=2879896k)

tmpfs(인메모리)를 사용하는 이유는 민감한 데이터를 노출시킬 수도 있는 디스크에 저장하지 않기 위해서다.

환경변수로 시크릿 항목 노출

볼륨을 사용하는 대신 컨피그맵에서 sleep-interval 항목을 노출한 것처럼, 시크릿의 개별 항목을 환경변수로 노출할 수 있다.

foo 키를 환경변수 FOO_SECRET으로 노출하고자 한다면 다음과 같이 컨테이너 정의에 추가하면 된다.

...
env:
- name: FOO_SECRET
  valueFrom:		# 변수는 시크릿 항목에서 설정된다.
    secretKeyRef:	
      name: fortune-https	# 키를 갖고 있는 시크릿의 이름
      key: poo		# 노출할 시크릿의 키 이름
 ...

쿠버네티스에서 시크릿을 환경변수로 노출할 수 있게 해주지만, 이 기능을 사용하는 것이 가장 좋은 방법은 아니다. 애플리케이션은 일반적으로 오류 보고서에 환경변수를 기록하거나 시작하면서 로그에 환경변수를 남겨 의도치 않게 시크릿을 노출할 수 있다.

이미지를 가져올 때 사용하는 시크릿 이해

파드를 배포할 때 이미지가 프라이빗 레지스트리 안에 있다면, 쿠버네티스는 이미지를 가져오기 위해 필요한 자격증명을 알아야 한다. 이를 어떻게 할 수 있는지 살펴보자

도커 허브는 공용 이미지 레지스트리 외에도 프리이빗 레지스트리를 만들수 있게 해준다. 프라이빗 저장소를 사용하는 파드를 실행하려면 다음 두 다시 작업이 필요하다.

  • 도커 레지스트리 자격증명을 가진 시크릿 생성
  • 파드 매니페스트 안에 imagePullSecrets 필드에 해당 시크릿 참조

도커 레지스트리 자격증명을 가진 시크릿 생성부터 살펴보자
시크릿을 생성하는 kubectl create secret 명령을 사용하지만 유형과 옵션이 다르다

$ kubectl create secret docker-registry mydockerhubsecret \
--docker-username=myusername --docker-password=mypassword \
--docker-email=my.email@provider.com

docker-registry형식의 mydockerhubsecret 시크릿을 생성하고 도커 허브 사용자 이름,패스워드,이메일을 지정하면 된다.

다음으로 파드 매니페스트 안에 imagePullSecrets 필드에 해당 시크릿 참조를 살펴보자. 쿠버네티스가 프라이빗 도커 허브 저장소에서 이미지를 가져올 때 시크릿을 사용하면 시크릿의 이름을 지정하는 것이 필요하다

pod-with-private-image.yaml

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:		# 프라이빗 이미지 레지스트리에서 이미지를 가져올 수 있도록 설정
  - name: mydockerhubsecret
  containers:
  - image: username/private:tag
    name: main

파드의 정의를 살펴보면 mydockerhubsecret 시크릿을 imagePullSecrets 항목으로 지정한다.

모든 파드에서 이미지를 가져올 때 사용할 시크릿을 모두 지정할 필요는 없다

사람들이 일반적으로 여러 다양한 파드를 시스템에서 실행하는 것을 감안하면, 모든 파드에 이미지를 가져올 때 사용할 시크릿을 지정하는 것이 필요한지 궁금할 것이다. 다행히 그렇지는 않다. 이미지를 가져올 때 사용할 시크릿을 서비스어카운트에 추가해 모든 파드에 자동으로 추가될 수 있게 할 수 있다. 추후에 알아보도록 한다.

profile
노옵스를향해

0개의 댓글