파드와 컨테이너의 통신네트워크 공유멀티컨테이너 파드 주의점초기화 컨테이너를 이용한 애플리케이션 시작예시2. 초기화 컨테이너를 이용해 설정값 구성하기어댑터 컨테이너를 이용한 일관성 있는 애플리케이션 관리예시 1. 로그 수집을 도와주는 사이드카 컨테이너예시 2. 헬스체크 기능, 지표 수집 사이드카 컨테이너외부와의 통신을 추상화하기: 앰배서더 컨테이너예시파드 환경 이해하기파드 속 컨테이너 경계 너머로 프로세스 공유마무리
멀티컨테이너 파드는 개념적으로는 간단히 파드 하나에 여러 개의 컨테이너를 실행하는 것임
대개는 애플리케이션 컨테이너와 함께 헬퍼 컨테이너가 추가되는 형태
파드와 컨테이너의 통신
- 파드는 하나 이상의 컨테이너가 공유하는
네트워크
및파일 시스템
을 제공하는 가상환경
- 각각의 컨테이너는 별도의 환경변수와 자신만의 프로세스를 가지며, 서로 다른 기술 스택으로 구성된 별개의 이미지를 사용할 수 있는 독립적 단위. 따라서 파드에 속한 컨테이너는 모두 같은 노드에서 동작함
- 같은 파드안에 있는 컨테이너는 네트워크를 공유하여, 모든 컨테이너가 같은 ip를 가짐. 포트를 다르게 해야 함
apiVersion: apps/v1 kind: Deployment metadata: name: sleep labels: kiamol: ch07 spec: selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: kiamol/ch03-sleep volumeMounts: - name: data mountPath: /data-rw. # 볼륨을 쓰기 가능으로 마운트 - name: file-reader image: kiamol/ch03-sleep volumeMounts: - name: data mountPath: /data-ro readOnly: true # 볼륨을 읽기 전용으로 마운트 volumes: - name: data emptyDir: {}
- 두개의 컨테이너를 파드에서 실행. Volume(
name: data
) 공유
- 공디렉터리 볼륨을 통해 파드 속 모든 컨테이너가 정보를 공유할 수 있게 하였음
네트워크 공유
spec: selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: kiamol/ch03-sleep - name: server image: kiamol/ch03-sleep command: ['sh', '-c', "while true; do echo -e 'HTTP/1.1 200 OK\nContent-Type: text/plain\nContent-Length: 7\n\nkiamol' | nc -l -p 8080; done"] ports: - containerPort: 8080 name: http
# sleep 컨테이너에서 서버 컨테이너로 통신 kubectl exec deply/sleep -c sleep -- wget -q -O - localhost:8080
- 하나의 파드안에서 각기 다른 컨테이너가 서로 localhost를 통해 포트로 통신이 가능함
위와 같은 네트워크 접근은 파드 안에서만 국한되지 않고 다른 파드에서 현 파드의 컨테이너 포트로 접근이 가능하다. 트래픽을 파드의 특정 포트로 전달하는 서비스를 만들면 이 포트를 주시하는 컨테이너가 요청을 받음
# 서버 컨테이너의 포트를 가리키는 서비스 생성 kubectl expose -f sleep/sleep-with-server.yaml --type LoadBalancer --port 8020 --target-port 8080
멀티컨테이너 파드 주의점
- 파드는 애플리케이션을 구성하는 한 단위이기에 애플리케이션의 단일 컴포넌트에 대응해야 함
- 한 파드 안에 서로 다른 애플리케이션을 함께 넣어서는 안됨
초기화 컨테이너를 이용한 애플리케이션 시작
- 위의 내용은 모든 컨테이너가 병렬로 실행되는 멀티컨테이너 파드였음
- 모두 동시에 실행되며, 모든 컨테이너의 상태가 Ready가 되어야 파드 역시 준비된 것으로 취급함
사이드카 패턴
: 추가 컨테이너(사이드카)가 애플리케이션 컨테이너(오토바이)를 지원하는 구도를 일컬음
초기화 컨테이너
: 애플리케이션 컨테이너보다 추가 컨테이너를 먼저 실행하여 애플리케이션 실행 준비를 하는 경우- 사이드카와 조금 다르고
- 파드 안에 여러개를 정의할 수 있으며
- 파드 정의에 기재된 순서대로 실행됨
- 각각의 초기화 컨테이너는 정해진 목표를 달성해야 다음 초기화 컨테이너를 실행
- 모든 초기화 컨테이너가 목표를 달성해야 애플리케이션 컨테이너나 사이드카 컨테이너를 실행함
- 초기화 컨테이너의 주요 역할은 애플리케이션 환경에 필요한 데이터를 기록하는 것이 많음
apiVersion: apps/v1 kind: Deployment metadata: name: sleep labels: kiamol: ch07 spec: selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: initContainers: - name: init-html image: kiamol/ch03-sleep command: ['sh', '-c', "echo '<!DOCTYPE html><html><body><h1>KIAMOL Ch07</h1></body></html>' > /data/index.html"] volumeMounts: - name: data mountPath: /data containers: - name: sleep image: kiamol/ch03-sleep - name: server image: kiamol/ch03-sleep command: ['sh', '-c', 'while true; do echo -e "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: 62\n\n$(cat /data-ro/index.html)" | nc -l -p 8080; done'] ports: - containerPort: 8080 name: http volumeMounts: - name: data mountPath: /data-ro readOnly: true volumes: - name: data emptyDir: {}
- 초기화 컨테이너 init-html 에서 html 파일을 생성하고 나서 server 컨테이너가 실행됨
예시2. 초기화 컨테이너를 이용해 설정값 구성하기
apiVersion: apps/v1 kind: Deployment metadata: name: timecheck labels: kiamol: ch07 spec: selector: matchLabels: app: timecheck template: metadata: labels: app: timecheck version: v2 spec: initContainers: - name: init-config image: kiamol/ch03-sleep command: ['sh', '-c', "cat /config-in/appsettings.json | jq --arg APP_ENV \"$APP_ENVIRONMENT\" '.Application.Environment=$APP_ENV' > /config-out/appsettings.json"] env: - name: APP_ENVIRONMENT value: TEST volumeMounts: - name: config-map mountPath: /config-in - name: config-dir mountPath: /config-out containers: - name: timecheck image: kiamol/ch07-timecheck volumeMounts: - name: config-dir mountPath: /config readOnly: true volumes: - name: config-map configMap: name: timecheck-config - name: config-dir emptyDir: {}
- 설정값을 컨피그맵과 비밀값, 환경 변수에 담은 후 초기화 컨테이너가 이들 설정값을 읽어들여 설정을 구성하고 구성된 설정 파일을 애플리케이션의 설정 파일 경로에 생성
- 초기화 컨테이너는 쿠버네티스 표준 설정 방식을 지원하지 않는 애플리케이션에 이런 방식을 지원하게 하는 데 큰 역할을 함
- 이것만으로는 모든 레거시 애플리케이션이 현대화된 플랫폼을 지원하게 하는 데 한계가 있고, 사이드카 컨테이너는 이처럼 초기화 컨테이너 만으로 레거시 애플리케이션을 현대화 할 수 없는 경우에 유용함
어댑터 컨테이너를 이용한 일관성 있는 애플리케이션 관리
예시 1. 로그 수집을 도와주는 사이드카 컨테이너
모든 애플리케이션은 어떤 형태로든 로그를 남기며, 또 그래야만 한다. 로그가 없다면 애플리케이션을 제대로 관리할 수 없기 때문이다.
쿠버네티스는 표준 출력으로 컨테이너 로그를 수집하는데, 애플리케이션에 표준 출력 대신 파일에 직접 로그를 남기거나 컨테이너 로그가 수집될 수 없는 채널을 이용해서 로그를 남긴다. → 이때 사이드카 컨테이너로 문제 해결
apiVersion: apps/v1 kind: Deployment metadata: name: timecheck labels: kiamol: ch07 spec: selector: matchLabels: app: timecheck template: metadata: labels: app: timecheck version: v3 spec: initContainers: - name: init-config image: kiamol/ch03-sleep command: ['sh', '-c', 'cp /config-in/appsettings.json /config-out/appsettings.json'] volumeMounts: - name: config-map mountPath: /config-in - name: config-dir mountPath: /config-out containers: - name: timecheck image: kiamol/ch07-timecheck volumeMounts: - name: config-dir mountPath: /config readOnly: true - name: logs-dir mountPath: /logs - name: logger image: kiamol/ch03-sleep command: ['sh', '-c', 'tail -f /logs-ro/timecheck.log'] volumeMounts: - name: logs-dir mountPath: /logs-ro readOnly: true volumes: - name: config-map configMap: name: timecheck-config - name: config-dir emptyDir: {} - name: logs-dir emptyDir: {}
- 사이드카 컨테이너가 하는 일은 로그가 출력되는 볼륨을 마운트하고 로그 파일의 내용을 tail 명령으로 표준 출력 스트림에 출력하는 것이 전부
- 애플리케이션 컨테이너가 파일에 출력한 로그를 다시 읽어 사이드카 컨테이너의 표준 출력으로 연결하기에 조금 비효율적
- 로그에 약간의 시차와 일부 디스크 용량이 낭비될 수 있지만, 애플리케이션 업데이트를 하면 볼륨이 차지하던 디스크 영역을 회수할 수 있음
예시 2. 헬스체크 기능, 지표 수집 사이드카 컨테이너
플랫폼을 통해 설정을 주입하고, 플랫폼에 로그를 출력하는 것은 컨테이너 플랫폼으로서 기본적인 사항. 하지만 플랫폼이 성숙함에 따라 컨테이너의 헬스체크 기능이나 애플리케이션의 상태나 성능 등을 파악하는 지표 수집 기능 등 더 많은 기능이 필요하게 됨
apiVersion: v1 kind: Service metadata: name: timecheck labels: kiamol: ch07 spec: ports: - port: 8080 targetPort: 8080 name: healthz - port: 8081 targetPort: 8081 name: metrics selector: app: timecheck type: ClusterIP --- apiVersion: apps/v1 kind: Deployment metadata: name: timecheck labels: kiamol: ch07 spec: selector: matchLabels: app: timecheck template: metadata: labels: app: timecheck version: v4 spec: initContainers: - name: init-config image: kiamol/ch03-sleep command: ['sh', '-c', 'cp /config-in/appsettings.json /config-out/appsettings.json'] volumeMounts: - name: config-map mountPath: /config-in - name: config-dir mountPath: /config-out containers: - name: timecheck image: kiamol/ch07-timecheck volumeMounts: - name: config-dir mountPath: /config readOnly: true - name: logs-dir mountPath: /logs - name: logger image: kiamol/ch03-sleep command: ['sh', '-c', 'tail -f /logs-ro/timecheck.log'] volumeMounts: - name: logs-dir mountPath: /logs-ro readOnly: true - name: healthz image: kiamol/ch03-sleep command: ['sh', '-c', "while true; do echo -e 'HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 17\n\n{\"status\": \"OK\"}' | nc -l -p 8080; done"] ports: - containerPort: 8080 name: http - name: metrics image: kiamol/ch03-sleep command: ['sh', '-c', "while true; do echo -e 'HTTP/1.1 200 OK\nContent-Type: text/plain\nContent-Length: 104\n\n# HELP timechecks_total The total number timechecks.\n# TYPE timechecks_total counter\ntimechecks_total 6' | nc -l -p 8081; done"] ports: - containerPort: 8081 name: http volumes: - name: config-map configMap: name: timecheck-config - name: config-dir emptyDir: {} - name: logs-dir emptyDir: {}
- 헬스체크용 8080번 포트와, 성능 지표용 8081번 포트를 연결
kubectl apply -f timecheck/timecheck-good-citizen.yaml # sleep 컨테이너에서 timecheck 애플리케이션의 헬스체크 api를 사용 kubectl exec deploy/sleep -c sleep -- wget -q -O - http://timecheck:8080 # sleep 컨테이너에서 성능 지표 API를 사용 kubectl exec deploy/sleep -c sleep -- wget -q -O - http://timecheck:8081
위의 예시에서는 sleep 컨테이너 내부에서 HTTP 엔드포인트를 호출했는데 쿠버네티스의 네트워크 모델에는 위계가 없어 서비스를 거치면 어떤 파드에라도 요청을 할 수 있기 때문임
애플리케이션의 네트워크 통신을 좀 더 세세하게 제어하고 싶다면 이 역시 사이드카 컨테이너로 가능함
- 애플리케이션 컨테이너에서 외부를 향하는 트래픽을 관리하는 프록시 컨테이너를 두면 된다.
외부와의 통신을 추상화하기: 앰배서더 컨테이너
애플리케이션이 localhost 주소로 네트워크 요청을 전달하면 이 요청을 앰배서더 컨테이너가 받아 처리하는 형태
네트워크의 제어권을 애플리케이션에서 이전해 오면 새롭게 할 수 있는 일이 많아짐. 프록시 컨테이너를 활용하면 서비스 디스커버리, 로드밸런싱, 연결 재시도, 심지어는 비보안 채널에 대한 암호화까지 가능하다.
예시
# dㅐ플리케이션 및 서비스 배치 kubectl apply -f numbers/ # 애플리케이션에 접근하여 무작위 숫자를 생성 # 웹 애플리케이션에서 다른 엔드포인트에 접근 가능한지 확인 kubectl exec deploy/numbers-web -c web -- wget -q -O - http://timecheck:8080
- numbers-web 파드는 클러스터 IP 서비스와 도메인 네임 numbers-api 를 통해 api에 접근함
- 하지만 외부 인터넷이나 다른 클러스터 IP 서비스 같은 주소에도 접근할 수 있기에 timecheck에도 접근이 가능함. 유출할 필요가 없는 정보가 노출됨
apiVersion: apps/v1 kind: Deployment metadata: name: numbers-web labels: kiamol: ch07 spec: selector: matchLabels: app: numbers-web template: metadata: labels: app: numbers-web spec: containers: - name: web image: kiamol/ch03-numbers-web env: - name: http_proxy value: http://localhost:1080 # 모든 트래픽이 앰배서더 컨테이너로 - name: RngApi__Url value: http://localhost/api # api 에 접근하기 위한 localhost 주소 - name: proxy image: kiamol/ch07-simple-proxy env: - name: Proxy__Port value: "1080" - name: Proxy__Request__UriMap__Source value: http://localhost/api - name: Proxy__Request__UriMap__Target value: http://numbers-api/sixeyed/kiamol/master/ch03/numbers/rng
- 웹 컨테이너의 모든 트래픽이 앰배서더 컨테이너를 거치도록 설정
- 앰배서더 컨테이너 패턴의 핵심
- 애플리케이션 컨테이너는 모든 서비스에 localhost 주소로 접근. 그리고 프록시 컨테이너를 거쳐 모든 네트워크 통신을 수행함
- 프록시는 모든 네트워크 호출을 기록하고, localhost 주소를 실제 주소로 매핑하며, 이 매핑에 등록되지 않은 다른 주소를 차단함
파드 환경 이해하기
- 파드는 하나 이상의 컨테이너를 감싸는 경계고, 컨테이너는 하나 이상의 프로세스를 감싸는 경계.
- 파드는 오버헤드 없이 가상화 계층을 추가하므로 유연하고 효율이 뛰어나다. 다만 이런 유연성의 대가는 언제나 그렇듯이 복잡도의 상승임
- 파드는 안에 있는 모든 컨테이너 준비가 끝나야 자신도 준비 상태가 됨. 그리고 서비스는 파드가 준비 상태가 되어야 트래픽을 전달
- 사이드카 컨테이너나 초기화 컨테이너는 애플리케이션에 일종의 안전 모드를 추가하는 것과 같다.
# 잘못된 초기화 컨테이너 정의. 초기화 컨테이너 재시작해도 정상 상태 되지 못하므로, 애플리케이션 컨테이너가 제대로 시작 x kubectl apply -f web-v2-broken-init-container.yaml kubectl get pod NAME READY STATUS RESTARTS AGE numbers-web-5ff679699c-sws2b 0/2 Init:CrashLoopBackOff 22 (99s ago) 91m kubectl logs -l app=numbers-web,version=v2 -c init-version sh: can't create /config-out/version.txt: Read-only file system kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE numbers-web 0/1 1 0 92m # 준비된 파드가 없음. kubectl get rs -l app=numbers-web NAME DESIRED CURRENT READY AGE numbers-web-5ff679699c 1 1 0 92m
재시작 조건
- 초기화 컨테이너를 가진 파드가 대체될 때, 새 파드는 초기화 컨테이너를 모두 실행. 따라서 초기화 로직은 반복가능해야 함
- 초기화 컨테이너의 이미지를 변경하면 파드 자체를 재시작함. 초기화 컨테이너는 다시 한번 실행되며, 애플리케이션 컨테이너도 모두 교체
- 파드 정의에서 애플리케이션 컨테이너의 이미지를 변경하면 애플리케이션 컨테이너가 대체됨. 초기화 컨테이너는 재시작 x
- 애플리케이션 컨테이너가 종료되면 파드가 애플리케이션 컨테이너를 재생성함. 대체 컨테이너가 준비될 때까지 파드는 완전한 동작상태 아니기에, 서비스에서 트래픽 못받음
파드 속 컨테이너 경계 너머로 프로세스 공유
파드 정의에 shareProcessNamespace: true 설정을 추가하면 파드의 모든 컨테이너가 컴퓨팅 공간을 공유하며 서로의 프로세스를 볼 수 있음
apiVersion: apps/v1 kind: Deployment metadata: name: sleep labels: kiamol: ch07 spec: selector: matchLabels: app: sleep template: metadata: labels: app: sleep version: shared spec: shareProcessNamespace: true containers: - name: sleep image: kiamol/ch03-sleep - name: server image: kiamol/ch03-sleep command: ['sh', '-c', "while true; do echo -e 'HTTP/1.1 200 OK\nContent-Type: text/plain\nContent-Length: 7\n\nkiamol' | nc -l -p 8080; done"] ports: - containerPort: 8080 name: http
마무리
- 이 장에서 초기화 컨테이너를 이용하여 애플리케이션 컨테이너에 필요한 환경을 준비하는 방법과 사이드카 컨테이너를 이용하여 애플리케이션을 수정하지 않고도 기능을 추가하는 방법을 배웠음
- 멀티컨테이너 파드는 애플리케이션을 확장할 때 많이 쓰이게 될 것.
initContainers를 sideCar 형태로 쓰면서, 죽지 않게 하려면 restartPolicy: Always 를 사용해라. 그러면 container 뜨는 순서도 보장할 수 있다.