지금까지 실습 과정에서 실행한 애플리케이션에는 어떠한 종류의 설정 데이터도 전달할 필요가 없었다.
거의 모든 애플리케이션은 빌드된 애플리케이션 자체에 포함하지 말아야 하는 설정(배포된 인스턴스별로 다른 세팅, 외부 시스템 액세스를 위한 자격증명 등)이 필요하다.
쿠버네티스에서 이런 애플리케이션을 실행할 때 설정 옵션을 어떻게 전달하는지 살펴보자.
새 애플리케이션 개발을 시작할 때 필요한 모든 설정을 애플리케이션에 포함하는 경우를 제외하면 일반적으로 명령줄 인수로 애플리케이션에 필요한 설정을 넘겨주는 것으로 시작한다.
예를 들어 MySql 공식 컨테이너 이미지에서는 루트 슈퍼 사용자 계정의 암호를 설정할 때 MYSQL_ROOT_PASSWORD 환경변수를 사용한다.
환경변수를 사용하는것이 왜 컨테이너에서 널리 사용될까? 도커 컨테이너 내부에 있는 설정 파일을 사용하는 것은 약간 까다롭다. 설정 파일을 컨테이너 이미지 안에 포함하거나 파일이 포함돼 있는 볼륨을 컨테이너에 마운트해야 하기 때문이다.
파일을 이미지 안에 넣고 빌드하는 것은 애플리케이션 소스 코드에 설정 내용을 넣고 하드코딩하는 것과 비슷하다. 그로 인해 인증 정보나 암호화 키와 같이 비밀로 유지해야 하는 내용을 포함하게 되면, 해당 이미지에 접근할 수 있는 사람이면 모두 볼수있기 때문이다
쿠버네티스는 설정 데이터를 최상위 레벨의 쿠버네티스 리소스에 저장하고 이를 기타 다른 리소스 정의와 마찬가지로 다른 파일 기반 스토리지에 저장 할 수 있다.
설정 데이터를 저장하는 쿠버네티스 리소스를 컨피그맵 이라고한다. 컨피그맵을 사용해 설정 데이터를 저장할지 여부에 관계없이 다음과 같은 방법으로 애플리케이션을 구성할 수 있다.
보안 관점에서 보면 자격증명, 개인 암호화 키 등 민감한 보안을 유지해야하는 유사한 데이터들은 쿠버네티스 시크릿이라는 또 다른 유형의 중요한 오브젝트를 제공한다.
하나씩 살펴보도록 하겠다.
도커에서의 명령어와 인자 정의를 먼저 살펴보자.
Dockerfile에서 두개의 지침은 다음 두 부분을 정의한다.
ENTRYPOINT 명령어로 실행하고 기본 인자를 정의하려는 경우에만 CMD를 지정하는 것이다.
이미지를 추가 인자를 지정해 Dockerfile 안의 CMD에 정의된 값을 재정의한다.
$ docker run <image> <arguments>
두 명령어는 두 가지 서로 다른 형식을 지원한다.
차이점은 내부에서 정의된 명령을 셸로 호출하는지 여부다.
exec 형식은 컨테이너 내부에서 node 프로세스를 직접 실행한다(셀 내부가아니다)
shell 형식은 shell에서 시작된다.
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-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: {}
인자를 지정하는 것은 명령줄 인자로 설정 옵션을 컨테이너에 전달하는 한 방법이다.
다음으로 환경변수를 이용하는 방법을 살펴보자.
앞선 예제처럼, 컨테이너화된 애플리케이션은 종종 환경변수를 설정 옵션의 소스로 사용한다.
쿠버네티스는 파드의 각 컨테이너를 위한 환경변수리스트를 지정할 수 있다.
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 # 노출할 시크릿의 키 이름
...
쿠버네티스에서 시크릿을 환경변수로 노출할 수 있게 해주지만, 이 기능을 사용하는 것이 가장 좋은 방법은 아니다. 애플리케이션은 일반적으로 오류 보고서에 환경변수를 기록하거나 시작하면서 로그에 환경변수를 남겨 의도치 않게 시크릿을 노출할 수 있다.
파드를 배포할 때 이미지가 프라이빗 레지스트리 안에 있다면, 쿠버네티스는 이미지를 가져오기 위해 필요한 자격증명을 알아야 한다. 이를 어떻게 할 수 있는지 살펴보자
도커 허브는 공용 이미지 레지스트리 외에도 프리이빗 레지스트리를 만들수 있게 해준다. 프라이빗 저장소를 사용하는 파드를 실행하려면 다음 두 다시 작업이 필요하다.
도커 레지스트리 자격증명을 가진 시크릿 생성부터 살펴보자
시크릿을 생성하는 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 항목으로 지정한다.
사람들이 일반적으로 여러 다양한 파드를 시스템에서 실행하는 것을 감안하면, 모든 파드에 이미지를 가져올 때 사용할 시크릿을 지정하는 것이 필요한지 궁금할 것이다. 다행히 그렇지는 않다. 이미지를 가져올 때 사용할 시크릿을 서비스어카운트에 추가해 모든 파드에 자동으로 추가될 수 있게 할 수 있다. 추후에 알아보도록 한다.