하나의 pod에 여러 개의 container들을 구동할 수 있다. 즉, pod는 여러 개의 container들을 한꺼번에 관리할 수 있는 것이다. 하나의 pod에 여러개의 container들을 올리는 여러 가지 방법들을 배워보고 container들끼리 어떻게 통신할 수 있는 지도 알아보도록 하자.
또한, 여러 개의 container들로 이루어진 pod를 관리하는 방법과 각 container에 접근하는 방법 또한 알아보도록 한다. 사실 단일 container로 이루어진 pod든 여러 개의 container들로 이루어진 pod든 pod를 관리하는 것은 크게 다를 바가 없다. 단지, 하나의 container에만 접근해야하는 방법을 여러 개의 container 중 하나를 선택하는 것 뿐이다.
추가적으로 중요한 design pattern에 대해서 알아볼텐데 가령 ambassador
, sidecar
그리고 adaptor containers
가 있다. 다음의 pattern들은 하나의 pod에 여러 개의 container들을 효율적으로 관리하기 위해서 배울 예정이다. 또한, kubernetes에서 volume을 어떻게 관리하는 지에 대해서 배울 것이다. Docker 또한 volumn을 제공하지만, kubernetes에서의 volume으로 같은 pod 내에서의 container들끼리 데이터를 공유할 수 있도록 할 것이다.
container들끼리 tight하게 연결되어 있을 필요가 있다면 하나의 pod에 container들을 묶을 필요가 있다. pod는 kubernetes cluster의 process 또는 하나의 application이다. 이러한 application이 잘 동작하기 위해서 여러 개의 container들을 필요로 한다면, container들을 하나의 pod를 통해 실행시키는 것이 좋다.
하나의 pod는 절대 나뉘어서 worker node에 동작하지 않는다. 즉, 한 worker node에서 실행된다는 것이고, pod에서 실행되는 container들은 같은 worker node에 있게 되어, 함께 관리된다.
언제 multi container를 사용할 지에 대한 이해를 위해 두 가지 간단한 application을 예시로 들어보도록 하자.
log forwarder
: NGINX와 같은 웹서버를 배포하여 특정 목적을 위한 dict형식으로 log들을 저장한다고 가정해보자. log들을 모으고 이를 forwarding시키고 싶을 것이다. 이를 위해서 Splunk forwarder
와 같은 container를 NGINX가 있는 pod에 같이 넣고 싶을 것이다. 이 log forwaring tool은 log들을 source로부터 목적지로 포워딩해주는 역할을 한다. 그리고 Splunk
나 Fluentd
또는 FileBeat
와 같은 agent를 배포하여 container로부터 log들을 잡아내고, ElasticSearch
cluster와 같은 central location으로 log들을 forwarding하는 것은 매우 일반적인 일이다. kubernetes에서 이러한 일은 동작을 위한 container 하나와 그 외의 container들 여러 개를 가지는 pod를 구동함으로서 이루어진다. 이렇게 log를 기록하는 container와 이를 다른 곳으로 expose하는 container 둘은 모두 하나의 pod에서 관리되어지며, 같은 node에 log forwarder로 같은 시간에 동작한다는 것이 보장된다.
proxy server
: multi-container pod의 또 다른 usecase는 application 앞에 NGINX web server를 reverse proxy 서버로 둔 경우이다. custom한 rule을 따르는 NGINX와 같은 미들웨어를 두어 web traffic을 실제 web application server로 전달하는 일은 매우 흔한 일이다. 이 두 container들을 하나의 pod로 묶어냄으로서 같은 node에 동작하는 두 개의 pod가 있는 것처럼 동작시킬 수 있다. 이 뿐만 아니라, 여기서 log forwarder
를 추가하여 log
를 기록하고 외부로 전달시킬 수 있다. 즉, 한 pod에 container가 3개가 되는 것이다. 이것이 kubernetes에서 하나의 pod에 여러 개의 container를 둘 수 있게 한 이유이다.
그럼 언제 multi-container pod를 만들면 안될까?
multi-container pod를 쓰는 가장 중요한 황금률은 하나의 pod에 있는 container들은 모두 같은 worker node에 선언된다는 것이다. 이는 배워나가면서 몸소 느껴보도록 하자.
대부분의 kubernetes resource들은 imperative syntax
와 declarative syntax
로 이루어져 있다. 두 개를 모두 호환하지만, 안타깝게도 multi-container pod는 declarative syntax
만 호환한다.
이는 yaml
file에 pod들의 선언를 포함해야하고, 모든 container들의 image를 적어야 한다는 것이다. 실행은 단일 container pod와 동일하게 kubectl create -f <filename.yaml>
을 사용한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx-container
image: nginx:latest
- name: busybox-container
image: busybox:latest
command:
- /bin/sh
- "-c"
- "sleep 60m"
이 yaml
manifest는 두 개의 container를 포함한 kubernetes pod를 만들어낸다. containers
에 list로 두 개의 container이름과 image를 써주기만 하면 된다.
busybox
의 경우에 container가 실행되면 command를 실행하고 바로 down되어버린다. 이는 busybox
에서 실행할 application이 없기 때문이다. 이를 위해서 command
부분에 sleep 60m
를 준 것이다. 60분뒤면 busybox
container가 더 이상 실행할 명령어가 없어서 종료하게 될 것이다.
만약, busybox
에 command를 주지않고 실행시키면 Error
, ContainerCreating
이 계속해서 반복된다. 이는 nginx
는 준비되어 실행되고 있는데, busybox
는 container가 만들어 실행되자마자 실행할 명령어가 없어서 종료되기 때문이다.
kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default nginx-pod 0/2 ContainerCreating 0 3s
확인해보면, READY
가 0/2
로 표시되어있는 것을 볼 수 있다. 조금 더 지난 후에는 1/2
에서 2/2
로 변할 것이다. 그러면 성공적으로 pod 배포가 완료된 것이다.
중요한 것은 하나의 pod에 있는 모든 container들은 같은 worker node에 배정된다는 것이다. 이는 하나의 pod가 여러 worker node에 널리 걸쳐있는 것이 아니라, 딱 하나의 worker node에 상주하기 때문이다. 즉, 같은 pod에 있는 모든 container들이 같은 woeker node 또는 docker daemon에 같이 스케줄링되고 실행된다는 것이다.
왜 한 pod에 있는 container들이 같은 worker node에 있는 것이 중요할까?? 같은 pod에 있는 container들은 같이 살기때문이다. 만약 해당 pod를 다운시켜버리면 모든 container들은 같이 죽을 것이다. 만약 pod를 다시 만든다면 kubelet
는 모든 container들을 함께 생성할 것이다.
만약 여러 개의 container들로 이루어진 pod에서 몇몇개 container들이 down된다면 어떻게 될까?
가장 일반적인 실수는 container의 image이름을 잘못적었을 때이다. busybox-container
의 image이름을 busybox:not-found-tag
로 바꾸고 pod를 만들어보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx-container
image: nginx:latest
- name: busybox-container
image: busybox:not-found-tag
command:
- /bin/sh
- "-c"
- "sleep 60m"
이제 다음의 yaml 파일을 실행시켜보도록 하자.
kubectl create -f multi-container-pod.yaml
pod/nginx-pod created
라는 로그를 통해 pod가 만들어졌음을 알 수 있다. 이는 Docker tag가 문제가 있음에도 kubernetes의 관점에서 yaml문법 자체에는 문제가 없기 때문에 실행이 되는 것이다. 그러나, pod는 만들어졌음에도 불구하고 kubelet
입장에서는 docker pull
명령어를 실행할 당시 잘못된 tag로 인해 image를 가져오지 못하고 error를 만났을 것이다.
kubectl get pods
로 현재의 상태를 가져와보도록 하자.
kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default nginx-pod 1/2 ImagePullBackOff 0 4m10s
1/2
상태에서 pod의 status가 나아지지 않는다. 이는 한 개의 container는 문제없이 실행되었지만, 다른 하나는 ImagePullBackOff
시에 문제가 발생했다는 것이다. kubectl describe pod nginx-pod
를 검색하면 events부분에 문제점이 무엇인지 볼 수 있다.
위의 과정을 통해 알 수 있는 것은 kubernetes가 다음의 과정을 따른다는 것이다.
etcd
에 entry를 만든다.mutli-container라고 해서 pod를 삭제하는 방법이 딱히 특별하거나 하진 않다. 단일 container와 같이 동일하게 두 개의 방법을 제공한다.
-f
옵션으로 지정해서 삭제하기kubectl delete pod <pod-name>
를 사용하여 삭제하기먼저 yaml
파일을 -f
옵션으로 지정해서 삭제하는 방법은 다음과 같다.
kubectl delete -f multi-container-pod.yaml
또는 kubectl delete pod
로 삭제할 수 있다.
kubectl delete pod nginx-pod
삭제 명령어가 실행되고 몇 초후에 pod가 내려가는 것을 볼 수 있다. 이 시간을 grace period
라고 부르는데, pod의 이름이 삭제되고 자원이 released되는 시간을 일컫는다.
pod를 삭제하는 것과 관련된 중요한 concept은 grace period
라 불리는 것이다. 이는 kubernetes에서 pod를 삭제하는 중에 pod의 이름을 어떻게 release하는 것과 관련되어있다. 단일 container pod든, 여러 개의 container로 이루어진 pod든 grace period을 갖으며, 이는 삭제 명령어를 사용하면 몇 초간의 시간이 걸리는 것으로 관찰된다. 이 grace period
는 --grace-period=0 --force
라는 옵션을 delete
명령어에 줌으로서 무시할 수 있다.
중요한 것은 같은 kubernetes cluster 내부에 같은 이름을 가진 pod들이 존재할 수 없다는 것이다. 이 때문에 kubectl delete
옵션으로 pod
이름을 주면 삭제가 가능한 것이다.
--grace-period=0 --force
옵션을 통해 pod를 삭제하면 바로 pod가 삭제되며 pod의 이름이 released된다. 그리고 또 다른 pod가 해당 이름을 가져가 쓸 수 있게 된다. 반면, --grace-period=0 --force
옵션을 사용하지 않으면 grace period
가 동작하고 pod의 이름은 삭제가 완료된 후에야 released된다.
kubectl delete pod nginx-pod --grace-period=0 --force
...
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "nginx-pod" force deleted
해당 명령어는 굉장히 조심스럽게 써야한다. 자주 사용할 일도 없고, 딱히 사용해야할 케이스도 없으니 알아만 두도록 하자.
한 pod에 여러 개의 container가 있다면 이들을 각각 접근할 수 있다. 이전에 만든 pod에서 nginx container에 접근해보도록 하자. 먼저 pod를 생성해주도록 하자.
kubectl create -f multi-container-pod.yaml
동작하는 pod의 container에 접근하기 위해서는 kubectl exec
명령어를 사용하면 된다. 마치 docker에서 특정 컨테이너 내부에 접속하기 위해서 docker exec
을 사용했듯이 말이다.
kubectl exec
명령어는 다음의 두 가지 파라미터를 요구한다.
1. 접근하고 싶은 container의 pod 이름
2. yaml manifest file에 적힌 container 이름 그 자체
pod의 이름을 아는 것은 kubectl get pod
를 통해서 쉽게 알 수 있다. 그러나 container의 이름을 알려주는 kubernetes 명령어는 딱히 없다.
이를 위해서 우리는 pod
에 대한 정보를 자세하게 알려주는 kubectl describe
명령어를 사용해보도록 하자. 해당 명령어는 pod
에 있는 자세한 정보를 알려주는데, container이름들과 어떤 이미지가 사용되었는 지도 알려준다.
kubectl describe pod nginx-pod
다음의 명령어를 치면
...
Containers:
nginx-container:
...
busybox-container:
...
두 개의 container 이름들을 찾을 수 있을것이다.
이제 해당 이름을 통해 container
에 접속해보도록 하자.
kubectl exec -it nginx-pod --container nginx-container -- /bin/bash
root@nginx-pod:/#
root@nginx-pod:/#
로 prompt창이 변경되었다면 접속한 것이다.
사용 방법이 매우 간단하다. exec
은 해당 object가 어떤 type인지 안알려줘도 된다. 바로 pod
이름이 나오고 --container
옵션으로 container 이름을 주고 -- /bin/bash
부분으로 container
내부에 실행할 어플리케이션을 적어준다. -it
으로 썼으니 해당 어플리케이션에 prompt가 붙게된다. 만약 -it
옵션을 안써주면 한 번 해당 어플리케이션을 실행하고 끝나게 된다. 참고로 -- /bin/bash
가 아니라 -- bash
라고 해도 된다.
만약, shell을 통해서 container에 직접 접속하고 싶은 것이 아니라, 해당 container 내부에 있는 어플리케이션을 한 번만 실행하게 하고 결과를 가져오고 싶다면 -it
없이 쓰고 -- bash
부분을 어플리케이션 코드로 바꾸면 된다. 가령, ls
를 서서 파일 구조를 보고 싶다면 다음과 같이하면 된다.
kubectl exec nginx-pod --container nginx-container -- ls
bin
boot
...
var
Dockerfile
을 보면 ENTRYPOINT와
CMD`가 있는데, 이를 통해서 container를 시작하면 실행할 명령어를 지정할 수 있다.
ENTRYPOINT
는 docker container가 실행할 주요 명령어이다.CMD
는 ENTRYPOINT
명령어에 들어갈 파라미터를 변경하는데 사용된다.가령 다음과 같이 container가 만들어지면 sleep
명령어를 30초간 수행하도록 만들 수 있다.
#~/Dockerfile
FROM busybox:latest
ENTRYPOINT: ["sleep"]
CMD ["30"]
재밌게도 CMD
부분은 docker run
명령어를 통해서 파라미터를 넘길 수 있다. 다음의 경우를 보자
docker run temp-docker-file:latest 60
docker run temp-docker-file:latest # 30초간 sleep된다.
반면 kubernetes에서는 ENTRYPOINT
와 CMD
를 pod를 정의한 YAML
manifest file을 통해 override할 수 있다. 이를 위해서, 두 가지 선택적인 key들을 yaml configuration에 추가할 수 있다. 하나는 command
이고 하나는 args
이다.
이는 굉장한 특징인데, Dockerfile
에 있는 ENTRYPOINT
와 CMD
를 실행하고 싶을 때는 오버라이딩을 하지 않아도 되고, 바꾸고 싶다면 kubernetes를 통해서 오버라이딩하면 된다.
다음은 새로운 manifest file로 busybox
image의 default ENTRYPOINT
와 CMD
파라미터들을 변경하여 busybox
container를 60초간 sleep
시킨다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-busybox-with-custom-command-and-args
spec:
initContainers:
- name: my-init-container
image: busybox:latest
command: ["sleep", "15"]
containers:
- name: nginx-container
image: nginx:latest
- name: busybox-container
image: busybox:latest
command: ["sleep"] # ENTRYPOIKNT
args: ["60"] # CMD
initContainers
부분은 다음 부분에서 배워보도록 하고, containers
를 보도록 하자.
busybox-container
에서 command
와 args
가 설정되어있다. https://hub.docker.com/_/busybox를 참고하면 딱히 뭔가 명령어를 실행하는 것이 아니라, 바이너리로 된 어플리케이션 파일을 그냥 실행하는 것이 전부이다. command
와 args
로 sleep
과 60
을 주었기 때문에 이제 busybox-container
를 실행되면서 60초간 sleep상태에 빠지게 된다.
실행 명령어는 다음과 같다.
kubectl create -f nginx-busybox-with-custom-command-and-args.yaml
kubectl get pods
로 확인해보면
kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default nginx-busybox-with-custom-command-and-args 2/2 Running 5 8m45s
계속해서 반복해서 pod가 죽었다 살았다를 반복할 것이다. sleep 60
명령어를 완료한 busybox-container
에서 더 이상 실행할 바이너리 코드가 없어 container를 종료하기 때문에 pod가 계속해서 Restart되는 것이다.
command
와 args
는 매우 강력한 명령어이지만 다소 이해하기 어려울 수 있다. 왜냐하면 Dockerfile
만 보고 동작을 예상한 개발자들에게 예상과는 말도안되는 결과를 전달하기 때문이다. 때문에 이를 사용하려면 개발자들 간의 약속은 필수이다.
만약 commnad
나 args
중 하나가 생략되면 이에 상응하는 ENTRYPOINT
나 CMD
가 실행된다.
이제 initConatiner
가 어떤 역할을 하고 어떤 부분인지 알아보도록 하자. 다음으로 initContainers
를 통해서 추가적인 side container를 pod에서 구동하여 주요한 것들을 설정하는 방법에 대해서 알아보자.
initContainers
는 kubernetes pod에 의해 제공되는 feature로 실제로 container들을 실행하기 이전에 setup script를 실행하기 위해서 사용한다. initContainers
부분을 pod yaml manifest file에서 정의할 수 있는 추가적인 side container들로 생각할 수 있다. 이는 pod가 시작될 때 가장 처음에 시작되며, 완료되면 pod가 주요 container들을 실행시키기 시작한다.
initContainers
에도 하나의 pod에 여러 개의 container들을 실행시킬 수 있지만 이는 병렬적으로 실행되는 것이 아니라, 하나 하나씩 실행되므로 오해하지 말도록 하자. 즉, 하나 실행되고 하나 실행된다는 것이다. 일반적으로 initContainers
는 git repo에서 application code를 pull하기 위해서 많은 사용되며 volumne mounts를 사용하여 main container에게 git code를 넘겨주는 역할을 한다. 또는 setup script를 실행한다.
initContainers
는 자기 자신의 docker images를 가지고 있기 때문에 main container의 일부 configuration을 initContainers
에 넘기고 main container는 가능한 작게 유지하는 것이 가능하다. 이를 통해 container에서 불필요한 tool들을 가지지 않도록 할 수 있다.
아래의 파일은 이미 이전에 살펴본 파일로 initContainers
를 사용해 pod가 실행되면 main container만들기 이전에 15초간 sleep하도록 한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-busybox-with-custom-command-and-args
spec:
initContainers:
- name: my-init-container
image: busybox:latest
command: ["sleep", "15"]
containers:
- name: nginx-container
image: nginx:latest
- name: busybox-container
image: busybox:latest
command: ["sleep"] # ENTRYPOIKNT
args: ["60"] # CMD
initContainers
역시도 다른 main container들처럼 image를 갖고 name를 갖으며 command
와 args
를 가질 수 있다. 즉 container라는 측면은 달라지지 않는다.
위의 yaml manifest를 실행시키고, 다음의 명령어로 감시해보도록 하자. 참고로 -w
는 watch
로 실시간으로 변화를 감지하는 명령어이다.
kubectl get pod nginx-busybox-with-custom-command-and-args -w
맨 처음에는 Init:0/1
인데, 이는 InitContainers
가 실행되는 부분으로 15초가 지난 뒤에 main container에 대한 PodInitializing
이 실행되고 main container들이 전부 실행되고 나면 Running
이 변할 것이다.
중요한 것은 initContainers
가 실패하면 kubernetes에서 main container를 실행시키지 않는다는 것이다.
initContainers
는 유용하지만 실패하면 전체가 구동되지 않는다는 리스크도 있기 때문에 되도록 쓰지 않는 것이 좋다. 만약, 초기에 설정이 필요한 코드가 있다면 유용하게 사용하고 불필요한 경우는 억지로 사용하지 않는 것이 좋다. 해당 옵션이 필수로 있지 않는 이유도 바로 실패할 경우의 리스크가 크기 때문이다.
kubectl logs
를 사용해서 pod에 있는 특정 container의 로그를 확인할 수 있다.
컨테이너화된 application에서 로그를 보여주는 가장 흔한 방법은 로그 정보를 stdout
으로 전달하는 것이고 이것이 기본적으로 kubernetes의 kubectl logs
명령어를 사용할 때, docker가 보여주는 것이다.
kubectl logs
명령어는 특정 container의 stdout
를 가져올 수 있다. 이를 위해서는 이전에 kubectl exec
처럼 pod
의 이름과 container
의 이름이 필요하다.
logs
는 exec
과 마찬가지로 pod
라는 object type을 굳이 붙여주지 않아도 된다. 따라서 바로 pod이름을 붙여주고 --container
에 container 이름을 붙여주면 된다.
kubectl logs nginx-busybox-with-custom-command-and-args --container nginx-container
위 명령어를 입력하면 해당 container의 로그가 쭉 나올 것이다.
재밌는 것은 initContainers
역시도 마찬가지로 동작한다는 것이다. 이를 활용해 initContainer
에서 혹시나 설정이 잘못되었는 지 확인할 수 있다.
만약
--container
option을 주지않으면 pod에 있는 모든 container들의 log들을 가져올 것이다. 단일 container로 동작하는 pod의 경우에는 container가 하나이므로--container
를 쓰지 않는 것이 좋지만, multi-container pod에서는 지정해야 원하는 log만 나올 것이다.
logs
에는 이런저런 재밌는 옵션들이 많은데, -f
옵션을 사용하면 해당 prompt가 stdout
과 붙어서 프린팅되는 로그들을 실시간으로 가져온다.
--since=?
라는 옵션을 쓰면 ?
시간동안의 로그를 가져온다.
다음은 10분 동안의 로그를 가져오는 옵션이다.
kubectl logs nginx-busybox-with-custom-command-and-args --container nginx-container --since=10m
지금이야 별로 로그량이 많지않아서 그렇지만 나중에 로그들이 많아지면 해당 명령어가 꽤나 유용하게 쓰인다.
--tail=?
을 사용하여 ?
개의 line들을 가져올 수 있다.
kubectl logs nginx-busybox-with-custom-command-and-args --container nginx-container --tail=10
다음의 명령어로 가장 최근의 10
개 line을 가져올 수 있다.
이외에도 다양한 명령어들이 있으니 참고하도록 하자.
https://jamesdefabia.github.io/docs/user-guide/kubectl/kubectl_logs/
docker에도 volumn이 있지만 kubernetes에서의 volumn과는 다르다. 이들이 같은 용도를 위해서 만들어졌다해도 같지는 않은 것이다.
우리의 pod에는 두 가지 container가 있는데, 하나는 nginx
이고 하나는 busybox
이다. nginx
에 있는 log directory를 busybox
의 directory에 마운팅하도록하여 이들의 log directory를 공유하도록 할 것이다. 이렇게 함으로서 이 두 컨터이너들은 directory를 공유하도록 관계를 생성하는 것이다.
kubernetes는 두 가지 종류의 volumes를 가진다.
PersistentVolume
: 이는 더욱 advanced한 내용으로 kubernetes의 Persistent Storage
에 대해 언급할 때 배울 것이다.이 둘은 서로 다르다는 것을 명심하도록 하자. PersistentVolume
은 그 자체가 resource이다. 반면에 volumes
는 pod configuration이다. 이름이 알려주듯이 PersistentVolume
은 persistent한 성격이 있는 반면에 volumes
는 persistent하지 못하다. 그러나 항상 persistent하지 않는 것은 아니라는 것을 명심두도록 하자.
간단히 말해서, volumes
는 pod의 생명주기에 바운딩된 storage라는 것이다. pod를 생성하게 되면 volumes를 만들게 될 기회가 있고, 이를 pod안에 있는 container들에 붙일 수 있다. volumes
는 pod의 생명주기를 따르는 storage에 불과하다. pod가 삭제되는 순간 volumes
는 같이 삭제될 것이다.
volumes
는 같은 pod에 동작하는 container사이에 file들과 directory를 공유하는 가장 좋은 방법이다.
volumes
는 pod의 생명주기에 바인딩되는 것이지 container의 생명주기에 바인딩되는 것이 아니다. 만약 containr가 크러쉬나도 volume은 살아있다. 이는 container가 크러쉬나도 container를 담고 있는 pod를 crash내지 않기 때문이다. 따라서 volume은 삭제되지 않을 것이고 pod가 살아있는 한 volume은 같이 살아있을 것이다.
docker에서 제공하는 volume의 개념은 단지 container를 구동하는 local machine의 directories를 공유하도록 마운트시키는 것이다. kubernetes 역시도 해당 아이디어를 차용하여 shared directories로 volume을 사용했다.
그러나, kubernetes는 또한 수많은 driver들의 지원을 가져왔는데, 이는 pod의 volumne을 외부 solution들과 함께 통합하는데 도움을 준다. 가령, AWS
의 EBS
volume은 kubernetes volume으로 쓰일 수 있다. 이외에도 다양한 kubernetes volume들이 있다.
awsElasticBlockStore
azureDist
gcePersistentDisk
gluesterfs
hostPath
emptyDir
nfs
persistentVolumeClaim
외부의 volume과 연결된다는 것은 volume
이 무조건 pod의 생명주기를 따른다는 것이 아니라는 것이다. 때문에, 이전에 volume
이 무조건 persistent하지 않는건 아니라고 한 것이다. 만약 AWS
의 EBS와 연결되면 pod가 삭제되어도 backend provider인
AWS가 망하지않는 한
volume은 계속해서 남아있다. 이는 나중에 언급해야할 부분이고, 지금은
volume`의 가장 간단한 부분을 보도록 하자.
volume
중 가장 많이 쓰이는 volume driver인 emptyDir
과 hostPath
를 알아보도록 하자. 이후에 persistentVolumeClaim
에 대해서도 알아보도록 하자. 왜냐하면 이는 다른 volume들과는 약간 다른점이 있기 때문이다.
emptyDir
volume을 만들고 마운팅하기emptyDir
volume type은 가장 흔히 사용되는 volume이다. 이름에서 의미하듯이 empty directory이며 pod가 생성될 때 만들어진다. 따라서, pod에 동작 중인 각 container의 location에 마운트시킬 수 있다. 해당 volume은 실제 pod를 호스팅하는 worker node에 들어가는 것이 아니라, pod에서 임시로 만드는 것이다. 따라서 해당 worker node에서 찾아봤자 안나온다.
하나의 pod에 두 개의 container를 만들도록 하며, 두 pod 모두 같은 volume을 보도록하자.
two-nginx-with-emptydir-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: two-containers-with-empty-dir
spec:
containers:
- name: nginx-container
image: nginx:latest
volumeMounts:
- mountPath: /var/i-am-empty-dir-volume
name: empty-dir-volume
- name: busybox-container
image: busybox:latest
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 30; done;"]
volumeMounts:
- mountPath: /var/i-am-empty-dir-volume
name: empty-dir-volume
volumes:
- name: empty-dir-volume
emptyDir: {} # Initialize an empty directory , the path on the worker node
해당 pod는 kubernetes cluster에 만들어질 것이다. 복잡한 만큼 imperative syntax로는 처리가 불가능하다.
spec
은 pod
가 관리할 요소들인데 volumes
도 container와 동등한 요소이므로, 같은 라인에 있다. volumes
로 이름과 emptyDir
이라는 옵션을 주면 비어있는 directory를 만들겠다는 것이다. volume
에 대한 spec을 정해준다해서 만들어지는 것이 아니다. 이 spec을 따르는 volume
을 만드는 것은 container
의 몫이다. container
에서 volume
을 spec.containers
에 있는 container들의 volumeMounts
에 넣어주도록 하자. volumeMounts
는 mountPath
와 name
으로 이루어져 있으며 mountPath
는 name
이라는 volume
을 어디에 적재할 지에 대한 위치를 쓰는 것이다.
현재 mountPath
는 worker node의 /var/i-am-empty-dir-volume
에 두겠다는 것이다. 두 container 모두 /var/i-am-empty-dir-volume
으로 volume
을 만들어주었다.
이제 실행해보도록 하자.
kubectl create -f two-nginx-with-emptydir-pod.yml
잘 만들어졌는 지 확인해보도록 하자.
kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default two-containers-with-empty-dir 2/2 Running 0 10m
이제 volume
이 잘 만들어졌는지 확인해보도록 하자. kubectl exec
명령어를 사용해서 ls
명령어를 실행해보도록 하자.
kubectl exec two-containers-with-empty-dir --container nginx-container -- ls /var/
kubectl exec two-containers-with-empty-dir --container busybox-container -- ls /var/
둘 다 i-am-empty-dir-volume
이 있을 것이다. i-am-empty-dir-volume
이 마운트된 것을 확인할 수 있었다.
그럼 이제 i-am-empty-dir-volume
에 파일을 만들면 과연 container들끼리 file을 공유할 수 있는 지 보도록 하자.
kubectl exec two-containers-with-empty-dir --container busybox-container -- /bin/sh -c "ech
o 'hello world' >> /var/i-am-empty-dir-volume/hello-world.txt"
위의 명령어를 통해 busybox-container
에서 /var/i-am-empty-dir-volume/hello-world.txt
파일을 만들 수 있다.
다음의 명령어를 통해서 nginx-container
에서 `/var/i-am-empty-dir-volume/hello-world.txt' 파일을 확인할 수 있다.
kubectl exec two-containers-with-empty-dir --container nginx-container -- cat /var/i-am-empty-dir-volume/hello-world.txt
hello world
hello world
라는 결과를 얻었으니 성공한 것이다.
서로 다른 container에서 file을 만들고 확인하는 것이 성공하였으니, 이들은 서로 file을 공유하는 shared directory를 얻었다고 할 수 있다.
만약, pod가 삭제되면 volume도 삭제될 것이고 해당 파일은 영영 recovery할 수 없게 될 것이다.
다음으로 hostPath
라는 volume type에 대해서 알아보도록 하자. 이름에서 알 수 있듯이 host machine
의 특정 directory를 container에 volume으로 마운팅할 수 있다는 것인데, host machine
이 바로 pod가 동작 중인 worker node를 말한다.
hostPath
volume은 pod안의 container에게 host machine에 있는 directory를 마운트하도록 해준다. host machine은 kubernetes worker node로 pod를 실행하는 machine을 말한다. 다음의 예시를 보도록 하자.
필자의 경우 Minikube
와 마찬가지인 상태로 master node(control plan e)와 worker node가 동일한 1개의 node로 구성된 cluster이므로, 명령어를 입력하는 현재의 local machine이 host이다.
hostPath
를 사용할 때 주의할 점이 있는데, 이를 사용하게되면 pod와 해당 worker node 사이의 강력한 관계가 생겨버린다는 것이다. 이로 인해 state가 만들어지게되며 문제가 발생할 수 있다. kubernetes 상에서 이러한 것을anti-pattern
이라고 생각할 수 있다.
때문에, hostPath
를 사용할 때는 state를 남기지 않도록 하는 것이 중요하다. kubernetes상의 pod는 쉽게 삭제될 수 있고, fault-tolerant하다. 또한, 다시 배포되는 것도 잦으며 이 때 다른 worker node로 스케줄링되는 것도 흔한 일이다. 그렇기 때문에 강한 relationship을 만드는 hostPath
에 대해서는 고민해볼 필요가 있다.
이제 hostPath
를 만들어보도록 하자. /tmp/hello.txt
파일을 nginx-container
의 /var/config/hello.txt
에 마운트하도록 하자.
이제 multi-container-pod-with-host-path.yaml
파일을 만들고 다음의 yaml
설정을 넣도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod-with-host-path
spec:
containers:
- name: nginx-container
image: nginx:latest
volumeMounts:
- mountPath: /var/config
name: my-host-path-volume
- name: busybox-container
image: busybox:latest
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 30; done;"]
volumes:
- name: my-host-path-volume
hostPath:
path: /tmp
volumes
에서 my-host-path-volume
이라는 이름을 가진 volumes
를 만들도록 하고, hostPath
를 /tmp
로 주도록 하였다. 참고로 /tmp
는 hostPath
의 위치이다.
nginx-container
는 volumeMounts
로 volume을 마운트하겠다는 선언을 하고, volume
의 이름은 my-host-path-volume
이며, mountPath
는 /var/config
로 두어 container 내부의 /var/config
와 host의 /tmp
는 동기화된다. 즉, 서로 shared한 directory를 갖게 된다는 것이다.
이제 hostPath
volume이 잘 만들어졌는 지 확인하기 위해 /tmp
에 hello.txt
파일을 만들도록 하자.
echo "Hello World" >> /tmp/hello-world.txt
다음으로, pod
를 실행시켜보도록 하자.
kubectl create -f multi-container-Pod-with-host-path.yaml
이제 해당 pod의 nginx-container에 접속하여 /var/confg
에 hello.txt
가 있는 지 확인하도록 하자.
kubectl exec -it multi-container-pod-with-hos-t-path --container nginx-container -- bash
nginx-container
의 bash
에 접속하였다면 다음을 통해 확인할 수 있다.
cat /var/config/hello-world.txt
Hello World
/tmp
에 있는 hello.txt
가 잘 적재된 것을 볼 수 있다.
이제 multi-container pod들과 관련된 pattern들에 대해서 알아보도록 하자.
multi-container pod의 가장 큰 대전제는 하나의 container는 하나의 기능만 한다는 것이다. 이를 위해 container를 나누는 다자인 패턴들이 생겼다.
ambassador design pattern은 multi-container pod에 적용되는 패턴으로 같은 pod에 두 개의 다른 container들을 정의할 때 사용한다. 두 개의 container는 다음과 같은데,
main container
라고 불린다.ambassador container
라고 불린다.main container
는 외부의 application과 통신할 수 있다. 가령, pod 외부에 있는 sql database에 접근하기 위해 외부와 interaction을 할 수 있는 것이다. 여기서 외부는 kubernetes cluster 외부를 말한다. 그런데, 만약 여러가지 설정이 필요한데, main container
를 수정할 수 없는 경우는 어떻게 해야할까?
이를 위해 ambassador container
가 존재하는 것이다. ambassador container
는 외부의 application에 대한 proxy역할을 한다. 즉, main container
가 ambassador container
에 external service에 대한 요청을 전달하고 ambassador container
가 받은 요청을 통해 external service
에 요청을 하여 응답을 전송하는 것이다. 즉, 위의 예시에서 ambassador container
는 sql database 서버의 proxy이며, sql database에 대한 host ip, port 정보들을 모두 가지고 있어 main container
가 sql을 요청하면 ambassador
가 sql proxy역할을 해준다.
ambassador container
를 사용하는 것은 좋지만, 이는 외부 API가 같은 kubernetes cluster에 존재하지 않을 때만 가능하다. 왜냐하면 같은 cluster 내에서 서로 다른 pod들끼리 통신하는 데에는service
라는 강력한 기능이 존재하기 때문이다.
그럼 왜 외부 external database에 접근하기 위해서 ambassador container
를 사용할까??
즉, main-container
가 무엇이든 간에, cluster외부 sql 서버를 사용하는 application이라면 공통적으로 해주어야 하는 설정들이 있을 수 있다. 이를 ambassador pattern을 통해 설정할 수 있으며, 회사망이나 private망에서 개발하는 경우 보안 이슈 때문에 골치를 앓을 수 있다. ambassador
pattern으로 이를 해결할 수 있는 것이다.
sql database server를 예시로 들었을 뿐이지, 이 뿐만 아니라 다른 external application들이면 모두 된다.
다음의 예제는 ambassador
design pattern을 보여준다. 외부 서버를 구동하고 있지 않으니 그냥 눈으로 보기만하도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-ambassador
spec:
containers:
- name: mysql-proxy-ambassador-container
image: mysql-proxy:latest
ports:
- containerPort: 3306
env:
- name: DB_HOST
value: mysql.xxx.us-east-1.rds.amazonaws.com
- name: nginx-container
image: nginx:latest
containers
로 두 개의 pod를 만들었고, 하나는 ambassador-container
이다. 재밌는 것은 containerPort
가 3306
으로 열려있고, DB_HOST
라는 env
를 설정한다. DB_HOST
가 바로 proxy서버가 사용할 진짜 sql database 서버인 것이다.
mysql-proxy-ambassador-container
는 ports.containerPort
로 3306
이 열려있다. 이는 localhost
로 접속이 가능하다는 것이고, main-container
인 nginx-container
에서 localhost:3306
으로 접속이 가능하다는 것이다. 따라서, sql
서버로 nginx-container
가 localhjost:3306
을 지정하고 sql요청을 전달하면 mysql-proxy-ambassador-container
에 요청이 들어와 DB_HOST
에 적힌 외부 서버로 요청을 대신 전달하고 응답을 받아 nginx-container
에 전달한다.
sidecar design pattern은 main container가 스스로 사용할 수 없는 feature들을 확장하고 싶을 때 사용하기 좋다.
sidecar container
를 main container
의 helper 또는 extension으로 생각하면 좋다. sidecar container
의 주요 목적은 main container
에게 새로운 feature를 추가하는 것이고 확장하는 것이다. 그러나 main container
의 원래의 기능을 변경하는 것이 아니다. ambassador
design patten과는 달리 main container
는 심지어 sidecar container
의 존재를 인지하지 못한다.
ambassador
design pattern과 마찬가지로 sidecar
design pattern도 최소한 두 가지 container들로 이루어져 있다.
main container
: application을 구동하는 것sidecar container
: 추가적인 기능을 main container
에 달아주는 것가장 단적인 예로, sidecar container
로 main container
의 기능을 모니터링하거나 log를 포워딩하는 agent들을 사용할 수 있다.
main container
의 log들을 다른 location에 전달하는 sidecar
를 만들기 위해서 다음의 3가지 것들을 고려해야한다.
main container
가 작성하는 log들의 위치(directory)를 알아야 한다.main container
의 log 위치에 접근하기 위해서 volume
을 만들어야 한다.sidecar container
를 적절한 configuration으로 런칭해야한다.해당 concept을 기반으로 main container
의 본래의 기능느 변경되지 않는다. 또한, sidecar
가 실패해도 main container
에게 까지 영향을 미치지 않으며 main container
는 계속해서 동작한다.
다음의 글을 통해 sidecar
에 대해서 더 알아보도록 하자. https://medium.com/bb-tutorials-and-thoughts/kubernetes-learn-sidecar-container-pattern-6d8c21f873d
adapter desing pattern
역시도 두 가지 이상의 container로 이루어진 pattern이다.
main container
이고adapter container
이다.해당 desing pattern은 main container
들이 data를 특정 형식으로 내보낼 때 사용되어야 한다. 해당 데이터는 특정 형식으로 변형되어 다른 application에 전달된다. 이름에서 제시하듯이 adaptor container
는 그저 adapt
이다.
sidecar
design pattern과 마찬가지로 두 container모두 같은 volume에 접근하도록 하여, A라는 format으로 된 main container
의 log 파일을 JSON
과 같은 다른 형식으로 변경해 외부로 내보내면 된다.
다음의 글을 통해 adapter
design patter에 더 자세히 알아보도록 하자.
https://www.weave.works/blog/kubernetes-patterns-the-adapter-pattern