[그림과 실습으로 배우는 쿠버네티스 입문] 6장. 쿠버네티스 리소스 만들고 망가뜨리기
|
6.1 Pod의 라이프사이클 알기
- https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
- Pod 의 매니페스트에 등록되면 노드에 스케쥴링됨
- 그러면 kubelet 이 컨테이너를 작동하고 완료 조건이 충족되거나 이상이 있으면 종료되는 것으로 Pod의 라이프 사이클이 끝나게 됨
- 가상 머신에서 애플리케이션을 실행하던 시절에 비해, 작동 및 정지가 쉬운 컨테이너를 사용하면서 애플리케이션의 라이프사이클이 단순해졌음
stateDiagram-v2
[*] --> Pending
Pending --> Running: 컨테이너 시작 완료
Pending --> Failed: 스케줄링/이미지 오류
Running --> Succeeded: 모든 컨테이너 정상 종료
Running --> Failed: 컨테이너 비정상 종료 / OOM 등
state Terminated {
[*] --> Succeeded
[*] --> Failed
}
Failed --> [*]
Succeeded --> [*]
| Value | Description |
|---|---|
Pending | The Pod has been accepted by the Kubernetes cluster, but one or more of the containers has not been set up and made ready to run. This includes time a Pod spends waiting to be scheduled as well as the time spent downloading container images over the network. |
Running | The Pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. |
Succeeded | All containers in the Pod have terminated in success, and will not be restarted. |
Failed | All containers in the Pod have terminated, and at least one container has terminated in failure. That is, the container either exited with non-zero status or was terminated by the system, and is not set for automatic restarting. |
Unknown | For some reason the state of the Pod could not be obtained. This phase typically occurs due to an error in communicating with the node where the Pod should be running. |
6.2 Pod의 다중화를 위한 ReplicaSet과 Deployment
flowchart TD
%% --- 첫 번째 단일 ReplicaSet 예시 ---
RS0[ReplicaSet]
RS0 --> RS0P1[Pod]
RS0 --> RS0P2[Pod]
%% --- Deployment 아래 두 개의 ReplicaSet (v1, v2) ---
D[Deployment]
D -->|v1| RS1[ReplicaSet]
D -->|v2| RS2[ReplicaSet]
%% v1 ReplicaSet의 Pods
RS1 --> RS1P1[Pod]
RS1 --> RS1P2[Pod]
%% v2 ReplicaSet의 Pods
RS2 --> RS2P1[Pod]
RS2 --> RS2P2[Pod]
RS2 --> RS2P3[Pod]
- Pod 를 직접 만드는 것은 실제 운영 환경에서는 권장하지 않음
- Pod 만으로는 컨테이너의 다중화가 불가능하기 때문에 본격적인 운영 환경에는 적합하지 않음
- 그래서 Deployment 라는 리소스를 사용함
- Deployment 는 ReplicaSet 이라는 리소스를 만들고, ReplicaSet 이 Pod 를 만듦
6.2.1 ReplicaSet
- ReplicaSet 은 지정한 수만큼 Pod 를 복제하는 리소스
- Pod 리소스와 다른 점은 Pod 를 복제한다는 점
- 복제할 Pod 의 수를 replicas 로 지정함
- ReplicaSet 매니페스트의 예는 다음과 같음
| |
6.2.2 Deployment
flowchart TD
%% ===========================
%% 1) ReplicaSet 단독 업데이트 (위쪽 그림)
%% ===========================
subgraph RSUpdate[ReplicaSet의 컨테이너를<br/>업데이트할 때]
direction LR
RS_old[ReplicaSet]
RS_old --> RSP1_old[Pod]
RS_old --> RSP2_old[Pod]
RS_new[ReplicaSet<br/>v2]
RS_new --> RSP1_new[Pod]
RS_new --> RSP2_new[Pod]
end
flowchart TD
%% ===========================
%% 2) Deployment의 Rolling Update (아래쪽 그림)
%% ===========================
subgraph DeployUpdate[Deployment의 컨테이너를<br/>업데이트할 때]
direction LR
D[Deployment]
D -->|v2| RS1[ReplicaSet]
D -->|v1| RS2[ReplicaSet]
RS1 --> RS1P1[Pod]
RS1 --> RS1P2[Pod]
RS2 --> RS2P1[Pod]
RS2 --> RS2P2[Pod]
end
- Deployment 와 ReplicaSet 의 차이는 뭘까?
- 본격적인 운영 환경에서는 단순히 Pod 를 복제하여 다중화하는 것뿐만 아니라, Pod 를 ‘무중단 업데이트’ 해야 함
- ReplicaSet 이 만드는 Pod 의 컨테이너 이미지를 v1 에서 v2 로 변경하려면, v2 의 Pod 를 생성하는 ReplicaSet 을 새롭게 만들어야 함
- v1 에서 v2 로 원활하게 전환하기 위해서는 이들 ReplicaSet 을 연결해주는 상위 개념이 필요함
- 이처럼 ‘여러 ReplicaSet 을 연결하는’ 것이 Deployment 임
| |
| |
- RollingUpdateStrategy 는 Pod 이나 ReplicaSet 에는 없고, Deployment 에만 있는 항목
- 현재는 설정 값이 없어 기본값이 적용됨
StrategyType
- StrategyType 은 Deployment 의 Pod 업데이트 전략을 지정하는 옵션
- Recreate 와 RollingUpdate 중 하나를 선택할 수 있음
- Recreate
- 모든 Pod 를 동시에 업데이트
- RollingUpdate
- Pod 를 순차적으로 업데이트
- RollingUpdateStrategy 를 지정할 수 있음
- 기본 값으로 RollingUpdate 가 사용되며, 25% max unavailable, 25% max surge 가 설정됨
RollingUpdateStrategy
- RollingUpdateStrategy 필드에는 RollingUpdate 의 구체적인 동작 방식을 지정함
- 쿠버네티스에서는 이를 지원하기 위해 ReplicaSet 보다 상위 개념인 Deployment 를 도입함
- 이를 통해 애플리케이션을 업데이트하는 중에도 운영을 계속할 수 있어 서비스 중단을 최소화할 수 있음
- RollingUpdateStrategy 에 지정할 수 있는 것은 maxUnavailable, maxSurge 두 가지
- maxUnavailable
- 최대 몇 개의 Pod 를 동시에 종료할 수 있는가
- 기본 값으로 지정되는 25%는 ‘전체 Pod 의 25% 까지 동시에 종료할 수 있음’을 의미함
- 퍼센트가 아닌 고정 값을 지정할 수도 있음
- maxSurge
- 최대 몇 개의 Pod를 새로 생성할 수 있는가
- RollingUpdate 는 오래된 Pod 를 종료하고 새로운 Pod 를 생성하면서 애플리케이션을 업데이트함
- maxSurge 가 크면 그만큼 여분으로 생성되는 Pod 가 늘어나기 때문에, 필요한 클래스터의 용량이 늘어나고 비용도 증가함
- 상황에 따라 적당한 설정이 필요함
- 저자는 예전에 maxSurge 설정 때문에 Pod 의 수가 너무 많아져 모든 노드의 용량이 소진되어 RollingUpdate 가 끝나지 않았던 적이 있었음
| |
| |
- RollingUpdate
| |
| |
6.2.3 [만들고 고치기] Deployment를 만들고 망가뜨리기
- 새로운 도커 이미지 추가
| |
hello-server:1.1.0를 새로 추가
| |
| |
6.3 Pod로의 접속을 도와주는 Service
flowchart LR
subgraph AsIs[Pod가 교체되면 접속 주소도 변경?]
%% 개별 Pod들
P1[Pod<br/>10.X.X.1]
P2[Pod<br/>10.X.X.2]
APP[애플리케이션]
APP --> P1
APP --> P2
end
flowchart LR
subgraph ToBe[도메인 이름으로 지정 가능, Pod 가 교체되어도 IP는 바뀌지 않음]
P1[Pod]
P2[Pod]
S[Service<br/>service-name.<br/>default.svc.cluster.local]
S --> P1
S --> P2
APP[애플리케이션] -->|10 . X . X . 5| S
end
- Deployment 는 IP 주소를 가지지 않음
- Deployment 로 만든 리소스에 접근하려면 각 Pod 에 할당된 IP 로 접근해야 함
- 그러면 RollingUpdate 기능을 사용한다고 해도 접속 중인 Pod 가 지워지면 연결이 끊어지게 됨
- Deployment 로 생성한 여러 Pod 에게로 적절히 라우팅하기 위해 Service 라는 리소스가 사용됨
| |
6.3.1 Service의 Type 알기
- kubectl get service 로 출력된 결과의 Type 은 여러 종류가 있고, 기본값은 ClusterIP
- ClusterIP
- 클러스터 내부의 IP 주소로 Service 를 공개함
- 이 Type 으로 지정된 IP 주소는 클러스터 내부에서만 접속할 수 있음
- 지정된 IP 주소란
kubectl get service의 CLUSTER-IP 컬럼에 출력되는 IP 주소
- 지정된 IP 주소란
- Ingress 라는 리소스를 사용하면 외부에 공개 가능
- NodePort
- 모든 노드의 IP 주소에서 지정한 포트 번호 (NodePort)를 공개함
- LoadBalancer
- 외부 로드 밸런서를 사용하여 외부 IP 주소를 공개함
- 로드 밸런서는 별도로 준비해야 함
- ExternalName
- Service 를 externalName 에 매핑함
- 예를 들면 호스트 이름이 api.example.com
- 이 매핑을 통해 클러스터의 DNS 서버가 해당 외부 호스트 이름의 값을 가진 CNAME 레코드를 반환하게 됨
- Service 를 externalName 에 매핑함
Type: ClusterIP
| |
Type: NodePort
- NodePort 를 사용하면 클러스터 외부에서도 접근이 가능해져서 port-forward 를 할 필요가 없어짐
- 준비하기
| |
- service
| |
- NodePort 는 모든 노드의 Port 와 연결하므로, port-forward 를 하지 않아도 hello-server 에 접근할 수 있음
- 매번 port-forward 를 할 필요가 없어 편리하지만, 접속하던 노드가 고장나거나 하면 접속할 수 없게 됨
- 로컬 개발 환경에서 사용하기에는 편리해서 좋지만, 실제 운영 환경에서는 ClusterIP 나 LoadBalancer 를 사용하는 것이 좋음
- 결국 InternalIP 를 통해 호출은 하지 못했다. 하지만 아래와 같이
colima ssh로 접속해서는 확인이 가능했다.
| |
6.3.2 Service를 사용한 DNS
- 쿠버네티스에서는 Service 용 DNS 레코드를 자동으로 생성해 주기 때문에 FQDN(정규화된 도메인 이름)을 기억해 두면 편리함
- Fully Qualified Domain Name
- 일반적으로 Service 는 다음 명령으로 접속할 수 있음
<Service 이름>.<Namespace 이름>.svc.cluster.local
| |
6.3.3 [망가뜨리기] Service 망가뜨리기
| |
- 트러블 슈팅에 대해 다시 정리
- 실제 운영 환경에서는 인터넷에 서비스를 공개를 하는 경우가 많아 원인을 파악하는 것이 더 어려움
- 원인을 파악할 때는 가능한 한 애플리케이션에서 가까운 곳부터 파악해 나가는 것이 좋음
- 이번 경우에는 다음과 같은 순서대로 살펴볼 것
Pod 내부에서의 애플리케이션 연결을 확인
- 여기에 문제가 있으면 Pod 내부에 문제가 있는 것
클러스터 내의 다른 Pod 에서 연결을 확인
- 여기에 문제가 있으면 Pod 의 네트워크에 문제가 있는 것
클러스터 내의 다른 Pod 에서 Service 를 통한 연결을 확인
- 1, 2 모두 문제가 없고 3에 문제가 있으면 Service 설정에 문제가 있는 것
만약 모두 문제가 없다면 클러스터 내부와 외부 연결 설정에 문제가 있는 것
Pod 내에서 애플리케이션 접속 확인하기
- 동작 중인 컨테이너에는 셸이 없으므로, 디버깅용 컨테이너를 사용하여 확인해야 함
- 먼저 Pod 의 이름을 확인
- 이번에는 모든 Pod 의 STATUS 가 동일하므로, 임의의 Pod 를 선택하여 이름을 복사
- 디버깅용 컨테이너를 실행하여
localhost에 접근함 - 외부에 공개된 포트 번호와 애플리케이션이 공개하는 포트 번호가 다르니 주의하기 바람
| |
- 문제없이 잘 동작했기 때문에 Pod 의 내부 문제는 아니라고 볼 수 있음
- 클러스터 내의 별도의 Pod 로부터 접속 확인하기
| |
- 클러스터 내 다른 Pod 로부터의 접속도 문제가 없음을 알 수 있음
- 클러스터 내 별도의 Pod로부터 Service를 통해 접속 확인하기
| |
- 서버에 연결할 수 없다는 오류가 출력됨
- Service 를 통해 접근할 때 문제가 발생하는 것을 알 수 있음
- 그러면 Service 의 설정을 확인
| |
- Selector 를 보면
app=hello-server가 아니라hello-serve로 되어 있음을 알 수 있음 diff를 통해 쉽게 확인 가능
| |
- 실제 운영 환경에서는
git으로 매니페스트를 관리하고,diff를 보면서 문제가 없는 매니페스트만을 적용해야 함 - 원래 사용하던 매니페스트를 다시 적용하여 문제를 해결하고 끝내자
| |
6.4 Pod의 외부에서 정보를 읽어들이는 ConfigMap
- ConfigMap 은 컨테이너 외부에서 내부로 값을 설정할 때 사용하는 리소스
- 예를 들어 데이터베이스 이름, 사용자 이름과 같이 환경에 따라 다른 값을 설정하고 싶을 때 사용
- ConfigMap 을 사용하는 세 가지 방법
- 컨테이너에서 실행할 명령어의 인자로 읽어들이기
- 환경 변수로 읽어들이기
- 볼륨을 통해 설정 파일 읽어 들이기
- 자주 사용하는 2번 과 3번을 설명, 1번은 공식 문서를 참고하자.
6.4.1 환경 변수로 읽어들이기
- 애플리케이션이 환경 변수를 읽도록 구현하면 이 방법을 통해 외부 설정 값을 읽어들이게 됨
- 이를 위해 hello-server 의 구현을 변경하여 포트 번호를 외부에서 지정할 수 있게 함
- 매니페스트를 통해 포트 번호를 변경해보자
| |
hello-server 1.2.0생성 및 추가
| |
- 리소스 생성
| |
- port 변경:
8081→5555
| |
6.4.2 볼륨을 통해 설정 파일 읽어들이기
- Pod 에 볼륨을 설정하면 보존해야 되는 파일을 저장하거나 Pod 간 파일을 공유하기 위해 파일 시스템을 사용할 수 있게 됨
- 이 책은 상태가 없는 (stateless) 애플리케이션을 다루기 때문에 볼륨에 대한 자세한 설명은 생략
- 하지만, 상태가 있는 (stateful) 애플리케이션은 데이터를 저장할 곳이 필요함
- → 이때 사용하는 것이 볼륨과 관련된 리소스
- https://kubernetes.io/docs/concepts/storage/volumes/
hello-server 1.3.0생성 및 추가
| |
- 매니페스트 적용
| |
6.4.3 [망가뜨리기] ConfigMap 설정으로 인한 장애!
- 먼저 hello-server-env.yaml 를 사용
| |
- 새로운 매니페스트를 적용
| |
- 애플리케이션에 장애가 발생한 것을 확인!
- 원인을 찾기 위해 Pod 의 상태를 확인
STATUS가CreateContainerConfigError로 된 것을 확인- Pod 의 상세 정보 확인
| |
- 마지막에
Error: couldn't find key HOST in ConfigMap default/hello-server-configmap가 출력된 것을 확인 - 즉, ConfigMap 에 HOST 라는 Key 가 없다는 뜻
- Deployment 의 매니페스트에서 HOST 라는 key 를 지정하고 있는 부분을 확인
| |
- Deployment 의 매니페스트는 hello-server-configmap 에 키가 HOST 인 데이터가 있다고 가정
- 실제로 있는지 확인
| |
- Deployment 는 HOST 라는 key 가 있다고 가정하고 있지만, ConfigMap 의 data 에는 PORT 만 있음
- ConfigMap 을 만들지 않거나, key 를 추가하지 않거나, 다른 ConfigMap 을 지정하는 것과 같은 실수가 가끔 발생함
- 하지만 이런 실수가 있더라도 Deployment 의 RollingUpdate 를 사용한다면, 애플리케이션에 대한 접속 장애가 발생하지는 않았을 것
- 무엇이 잘못되었는지 다시 한 번 Deployment 의 매니페스트를 확인
| |
- maxSurge 가 명시적으로 0으로 설정되어 있음
- 만약 maxSurge 가 1 이상이었다면 정상적으로 동작하는 Pod 가 남아 있을 것이기 때문에 문제가 발생하지 않았을 것
- 원인을 알았으니 해결해보자.
- 먼저 hello-server-destruction.yaml 에 HOST 를 추가
| |
| |
6.5 기밀 데이터를 다루기 위한 Secret
- 애플리케이션 외부에서 값을 설정하기 위해 ConfigMap 을 사용하지만, ConfigMap 을 볼 수 있는 모든 사람이 비밀 정보에 접근하는 것은 보안상 바람직하지 않음
- 이때 Secret 이라는 리소스를 사용하면 접근 권한을 분리할 수 있음
- Secret 의 데이터는 Base64 로 인코딩하여 등록해야 함
- macOS 는 기본적으로
base64가 명령어로 등록되어있음
- macOS 는 기본적으로
- Secret 을 Pod 에서 읽어들이는 방법은 두 가지
- 환경 변수로 읽어들이기
- 볼륨을 통해 설정 파일로 읽어들이기
6.5.1 환경 변수로 읽어들이기
- 생성한 데이터를 사용하여 매니페스트를 작성
| |
| |
6.5.2 볼륨을 사용해서 컨테이너의 설정 파일 읽어들이기
nginx-volume.yaml를 사용- 이 매니페스트는 NGINX 컨테이너 내 경로
/etc/config에서 Secret 을 읽어들임
| |
6.6 한 번만 실행하는 태스크를 위한 Job
- Job 은 한 번만 실행하고 싶은 Pod 를 위해 사용함
- Job 을 실행하면, Pod 의 실행이 성공할 때까지 지정한 횟수만큼 재시도를 수행함
- 또한 Pod 를 여러 개 동시에 실행할 수도 있음
| |
6.7 Job을 정기적으로 실행하는 CronJob
- CronJob 은 정기적으로 Job 을 생성하는 리소스
- Linux 의 cron 과 동일하게 동작하는 Job 이라고 생각하면 됨
- 정기적으로 실행하고 싶은 작업이 있을 때 이 리소스를 사용
- CronJob 은 Job 을 생성하고, Job 은 Pod 를 생성함
- schedule 필드에 Job 을 실행하는 시점을 기재
- 지정한 날짜와 시간대는 기본적으로 kube-controller-manager 의 시간대를 기준으로 함
- 그리고
.spec.timeZone에 특정 시간대를 지정할 수 있음
| |
cronjob-hello-server.yaml
| |