특수 컨테이너
특수 컨테이너
파드의 기본 기능은 컨테이너에 대한 관리이며, 이에 관련한 다양한 방식을 제공한다.
이번 시간에는 특수한 컨테이너들을 통한 방법들을 알아보도록 한다.
초기화 컨테이너
init container.
직관적으로만 봐도 초기화를 진행하는 컨테이너라는 것을 알 수 있다.
이걸 통해 앱 컨테이너가 동작하기 전에 기본적으로 구축해야 할 설정과 동작을 수행할 수 있다.
특징
개념 자체는 쉬우니 바로 특징으로 넘어가도록 하자.
- 초기화 컨테이너는 반드시 종료되어야만 한다.
- 앱 컨테이너가 프로세스의 지속적인 작동을 전제하는 것과 다르다!
- 여러 개의 초기화 컨테이너를 둘 수 있다.
- 각 컨테이너는 순서대로 동작하며, 반드시 이전 컨테이너가 성공적으로 종료되었을 때 다음 컨테이너가 실행된다.
- 초기화 컨테이너가 성공적으로 종료되어야 비로소 앱 컨테이너가 동작한다.
- 초기화 컨테이너가 실패하면 kubelet은 파드 속 컨테이너의 장애#재시작 정책(restartPolicy)에 따라 관리한다.
위와 같이 초기화 컨테이너가 실행 중일 때 파드는 Pending
상태에 머물러 있다.
다른 컨테이너와의 차이
- 리소스 제한 방식이 조금 다르다
- 앱 컨테이너와 리소스를 공유하지만, 동시에 돌아가는 일은 없다.
- 다만 볼륨에 선작업을 하는 듯이 작용하는 것은 가능하다.
- 라이프사이클, 컨테이너 프로브는 지원되지 않는다.
- 파드의 running 상태를 위해 종료되어야 하는 컨테이너이기 때문
- [초기화 컨테이너에 프로브 넣기](https://zerotay-blog.vercel.app/3.study/Kubernetes/실습 및 트러블 슈팅/초기화 컨테이너에 프로브 넣기/)
- 사이드카 컨테이너는 다르니까 거기에서 참고하라.
사용법
초기화 컨테이너는 다음의 세 가지 상황에서 매우 유용하게 사용할 수 있다.
- 앱 컨테이너가 띄워지기 이전 확실하게 선행해야 하는 조건이 있을 때
- 다른 서비스가 켜져야만 제대로 돌아가는 파드라고 쳐보자.
- 이걸 컨테이너 프로브#startupProbe로 해결하는 방법도 있겠지만, 이는 지속적인 통신을 유발하고, 불필요하게 컨테이너가 오래 떠있게 만든다.
- 초기화 컨테이너에서 처리해버리면 불필요하게 앱 컨테이너가 오래 지속될 필요도 없다.
- 앱 이미지에서 하기 싫은 작업을 선행하고 싶을 때
- 앱 이미지 자체에 없는 디버깅 툴이나 설정 프로그램을 여기에서 선제적으로 깔고 작업을 진행할 수 있다.
- 필요하다면 볼륨으로 나중에 넘기면 된다.
- 앱 컨테이너에서 보안 취약점이 될 여지가 있는 유틸리티를 두지 않아도 된다.
- 앱 이미지에서 할 수 없는 작업을 할 때
- 초기화 컨테이너는 일반 앱 컨테이너와 다르게 Secret의 값을 뜯어볼 수 있다.
용례
for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
다른 서비스가 확실히 만들어지기 전까지 기다리기.
이 예시는 앱 컨테이너에 nslookup
이 없어도 돼서 더 좋다.
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
Downward API를 사용해 파드의 정보를 앱 실행 전에 등록하고 싶을 때.
sleep 60
그냥 시간 기다리기
이밖에도 깃 주소를 받아서 앱 컨테이너에 볼륨으로 공유하기, 템플릿 툴을 사용해 설정 파일을 앱 컨테이너에 공유하기 등의 작업을 수행할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
위와 같이 until을 사용해서 내가 원하는 서비스가 올라올 때까지 대기를 시키는 방식은 매우 유용하다.
유의 사항
몇 가지 유의할 만한 사항이 있다.
초기화 컨테이너를 사용하는데 있어서 알아두면 둬야 할 상세 동작들이라고 할 수 있겠다.
재시작
restartPolicy에 대해
파드의 파드 속 컨테이너의 장애#재시작 정책(restartPolicy)을 따르지만, Always
인 경우에는 OnFailure
가 적용된다.
달리 말하자면 기본적으로 OnFailure
이지만 파드의 재시작 정책이 Never
인 경우에는 Never
로 적용된다고 표현할 수도 있을 것 같다.
초기화 컨테이너가 성공적으로 수행되도 재시작된다면, 앱 컨테이너가 영원히 동작할 수 없으니 어쩌면 당연하다.
다른 문서에서 애매하게 적혀 있어서 [초기화 컨테이너는 재시작되는가](https://zerotay-blog.vercel.app/3.study/Kubernetes/실습 및 트러블 슈팅/초기화 컨테이너는 재시작되는가/)하는 고민까지 하게 만들어준 공식 문서에 다시금 감사를 표한다..
앱 컨테이너 정책 | 초기화 컨테이너 정책 |
---|---|
Always | OnFailure |
OnFailure | OnFailure |
Never | Never |
이렇게 정리해볼 수 있을 듯하다. |
파드의 재시작
초기화 컨테이너는 파드가 동작하는 시점에서 가장 선행되는 프로세스이다.
파드가 동작하는 시점이란 파드를 위한 네트워크, 스토리지 세팅이 완료된 이후를 말한다.
그래서 파드=초기화 컨테이너 정도의 밀접한 관계를 맺는다고도 말할 수 있다.
달리 말해 파드가 재시작하면 모든 초기화 컨테이너도 다시 실행한다.
이건 당연하게 들린다.
그러나 1.20 버전 이전에는 그 역도 거의 성립하다시피 했다.
현재는 그렇지는 않다고 하는 듯하다.
다음은 파드가 재시작되는 경우.
- 파드의 인프라 환경이 재설정됨.
- 실질적으로 누군가 루트 권한으로 노드를 조작한 것과 동일하기에 특수한 케이스
- 초기화 컨테이너 이미지를 변경함
- 재시작 정책이
Always
인데 모든 초기화 컨테이너의 실행 정보가 가비지 컬렉팅됐고, 또한 모든 컨테이너가 종료되었음- 어차피 재시작을 걸긴 해야 하는데, 마침 초기화 컨테이너도 전부 실행 정보다 날아 버리면 그냥 파드가 재시작되는 ㄷㄷ
위 상황들이 궁금해서 조금 [초기화 컨테이너의 이미지 바꾸기](https://zerotay-blog.vercel.app/3.study/Kubernetes/실습 및 트러블 슈팅/초기화 컨테이너의 이미지 바꾸기/) 테스트를 해봤다.
문서와는 조금 다른 구석이 있는 것 같으니 문서 설명을 너무 맹신하지는 말자.
결국 1.20 버전 이후부터는 첫번째 경우 말고는 파드가 재시작되는 경우가 없는 것으로 생각하면 될 듯하다.
리소스 공유
각 파드는 적정한 리소스 값을 가진다.
그리고 그것은 당연히 내부 컨테이너가 가질 수 있는 리소스 값에 달려있다.
이걸 왜 초기화 컨테이너에서 언급하는가?
초기화 컨테이너 중에서 가장 높게 리소스 제한이 걸린 값을 유효 초기 요청/제한
이라고 부른다.
초기화 컨테이너 중 리소스 명시가 안 된 컨테이너는 이 값을 기준으로 제한이 걸린다.
이게 중요한 이유는 파드가 받을 수 있는 리소스의 값에 영향을 준다는 것이다.
파드의 리소스 스케줄링은 다음 중 더 높은 값을 토대로 이뤄진다.
- 모든 앱 컨테이너의 리소스 요청/제한 총량
- 유효한 초기 요청/제한
무슨 말이냐, 초기화 컨테이너는 파드의 라이프사이클에는 영향을 주지도 않는 주제에 파드의 리소스를 많이 걸어버릴 수도 있다는 것이다.
참고로 초기 컨테이너와 앱 컨테이너의 QoS 계층은 동일하다.
팁
케이스가 많지는 않지만, 초기화 컨테이너 역시 재시작될 가능성은 분명히 존재한다.
가령 모종의 이유로 컨테이너가 실패하게 된다던가 하는 상황이다.
그러니 멱등(idempotent)한 코드를 짜는 것이 중요하다.
EmptyDirs
에 이전 초기화 컨테이너가 실행한 결과로 인한 오작동에 대한 대비책은 마련되어 있어야만 한다.
activeDeadlineSeconds
를 써서 초기화 컨테이너가 무한 지속되는 상황을 미연에 방지할 수 있다.
그러나 이건 앱 컨테이너에도 적용되니까 한번 실행되고 끝나는 Job에 대해서만 적용하길 추천한다.
초기화 컨테이너끼리의 이름은 고유해야만 한다.
사이드카 컨테이너
기본적으로 사이드카 컨테이너라는 말은 sidecar pattern을 컨테이너 형태로 만든 것을 말한다.
공식 문서에서는 사이드카 컨테이너라고 페이지를 아예 하나 빼서 설명을 하는데, 이것이 초기화 컨테이너 정도로 쿠버네티스를 활용하는데 있어서 명시되어 활용되는 개념이 아니라는 뜻이다.
사이드카 컨테이너는 메인 컨테이너들의 옆에 꼭 달라붙어 로깅, 프록시 등의 보조적인 기능을 수행하는 컨테이너를 일컫는 표현일 뿐이다.
1.28 버전 이후로부터 쿠버네티스에서는 자체적으로 사이드카 구현 방식을 제공하기 시작했다.
초기화 컨테이너에 restartPolicy
를 Always로 걸어서 만드는 방식으로..
꽤나 기형적인데, 1.29버전 기준 베타 상태이다.
지금부터 설명하는 사이드카 컨테이너는 바로 이 네이티브 방식을 말한다.
엄밀한 구조로 보자면 초기화 컨테이너의 한 종류이며, 이 글에서 언급할 초기화 컨테이너는 사이드카 컨테이너가 아닌 초기화 컨테이너를 한정하여 표현한다.
개념
위에서 말했듯이 앱 컨테이너와 같은 파드 내에서 돌아가는 사이드 격의 컨테이너이다.
이것들은 로깅, 모니터링 등 앱 컨테이너에 보조적인 기능을 하기 위해 동작시킨다.
그러면서 앱 컨테이너에 영향이 가지 않도록 한다는 점이 주 사용 이유이다.
문서에서는 WAS 서버의 프론트인 웹서버를 사이드카로 두는 예시를 드나, 웹 개발자 입장에서는 그다지 와닿는 예시는 아닐 듯하다.
웹 서버나 was 서버나 핵심 로직을 품고 있는 경우도 많고, 애초에 현대의 개발 방식은 프론트와 백을 구분하기 때문이다.
어차피 앱 컨테이너와 같이 돌아갈 것이라면, 왜 굳이 이걸 따로 지정해서 사용하는가?
그냥 일반 컨테이너 하나를 더 만들어도 되는 게 아닌가 하는 궁금증이 있을 수 있다.
이건 더 알아보자.
사용 방법
위에서 말했듯이 restartPolicy
가 Always
로 명시된 초기화 컨테이너가 사이드카 컨테이너가 된다.
파드 속 컨테이너의 장애#재시작 정책(restartPolicy)은 본디 파드 스펙에 명시를 해야 하지만, 초기화 컨테이너 하위 스펙으로도 명시할 수 있고, 이때 이게 적용된다는 것이다.
이 녀석은 초기화 컨테이너와 다르게 앱 컨테이너가 시작되고 같이 실행되기 시작한다.
그렇게 특별한 능력을 가진 놈이었으면, 그리고 초기화 컨테이너와 다른 놈이었으면 그냥 이름을 분리하지 정말 애매하게 만들어둔 것 같다.
사이드카 feature gate가 활성화되어 있어야만 사용할 수 있는데, 기본값이 true이다.
디플로이먼트
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: alpine:latest
command: ['sh', '-c', 'while true; do echo "logging" >> /opt/logs.txt; sleep 1; done']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail -F /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
volumes:
- name: data
emptyDir: {}
Deployment 내 템플릿에 적용한 예시.
/opt라는 공간에 앱 컨테이너는 로그를 넣고, 사이드카 컨테이너에서 해당 로그를 실시간으로 출력한다.
잡
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: alpine:latest
command: ['sh', '-c', 'echo "logging" > /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail -F /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
restartPolicy: Never
volumes:
- name: data
emptyDir: {}
위와 비슷하다.
근데 여기에서 사이드카 컨테이너의 진미가 살짝 드러난다.
Job은 앱 컨테이너가 종료되면 그냥 종료되어버린다.
즉, 사이드카 컨테이너가 살아있건 말건 신경쓰지 않고 종료를 시킨다는 말이다.
이 덕분에, 파드의 생애 주기 자체에 영향을 조금도 주지 않으면서 생애 주기를 함께 하는 컨테이너를 만들 수 있게 되는 것이다.
생애 주기
사이드카 컨테이너는 파드의 전체 생애와 연결되어 있다.
이래서 앱 컨테이너와 분리된 환경에서 제어를 가하는데 유용하다.
참고로 컨테이너 프로브#readinessProbe를 명시하면 파드의 준비 상태에 영향을 준다.
시작
초기화 컨테이너이기 때문에, 마찬가지로 순서를 지정하는 것이 가능하다.
E-초기화 컨테이너보다 앞서는 사이드카 컨테이너일 때도 순서는 보장되기는 한다.
정확하게는 사이드카 컨테이너의 started
가 참일 때 다음 컨테이너가 실행되는 구조로, 컨테이너 프로브#startupProbe가 설정되었다면 해당 프로브가 완료되었을 때 실행된다.
아무튼 앱 컨테이너가 시작되기 이전에 시작되는 것은 보장된다는 것을 뜻하기도 한다.
애초에 사이드카 컨테이너도 초기화 컨테이너의 일종이기에 이 녀석이 제대로 시작돼야 앱 컨테이너가 실행될 수 있도록 kubelet이 조정한다.
종료
앱 컨테이너가 terminating 상태에 접어들 시점, 아무튼 파드가 사라질 때까지 사이드카 컨테이너는 함께 한다.
이 컨테이너의 생명 주기나 이런 것은 무조건 파드의 주기와 같다고 보면 될 것 같다.
파드가 정상 작동하는 동안 이 녀석은 항상 작동하고 있고, 파드가 죽을 때까지 이 놈은 버틴다.
효용
1.28버전 이전, sidecar pattern을 구현하는 사용자들은 대체로 다음의 방법을 활용했다.
- 파드 생애 주기보다 작은 생애 주기를 가진 사이드카
- 초기화 컨테이너에 박아 넣고 어떻게든 세팅을 잘하는 방식
- 애초에 사이드카로서 활용할 수 있는 방도가 많지 않고, 앱 컨테이너가 돌아가기 전에 종료되야 함
- 파드 생애 주기보다 같은 생애 주기를 가진 사이드카
- 그냥 사이드카 격으로 앱 컨테이너를 더 띄우는 방식
- 이건 파드 종료에 영향을 끼치는 컨테이너가 됨
위의 문제들을 조금 해소해주어 사람들이 많이 사용하는 패턴을 효율적으로 구현할 수 있도록 제공하는 것이 바로 이 사이드카 컨테이너이다.
그래서 다음의 두 가지 이점을 가진다고 보면 되겠다.
- 앱 컨테이너보다 빠르게 시작되므로 순서에 대한 확실한 보장
- 파드의 종료에 영향을 주지 않는 ㄹㅇ 쩌리 컨테이너
다른 컨테이너와의 비교
앱 컨테이너
어떻게 설계하냐의 차이지만, 대체로 사이드카에는 핵심 로직은 넣지 않는다.
이 친구는 파드의 종료가 결정되면 그냥 자비없이 사라져버린다.
초기화 컨테이너
초기화 컨테이너는 메인 로직이 실행되기 이전에 모두 종료되어야만 한다.
그렇기에 앱 컨테이너와 직접적인 상호작용이 절대 불가능한데, 사이드카는 앱과 동시에 올라가 있기에 가능하다.
또한 기본이 항상 같이 돌아가는 녀석이다 보니까 컨테이너 프로브 설정도 가능하다.