개요
Pod에서 어플리케이션의 동작을 구성하는 기본적인 요소들을 정리해보면 다음과 같다.
- Pod 생성
- Pod가 생성될 때 nodeSelector로 특정 노드를 지정할 수 있다.
- worker node의 resource를 얼마나 사용할지 설정한다.
- Docker Hub와 같은 image resgitry에서 이미지를 가져와 컨테이너를 생성한다.
- Pod 기동/운영
- Probe를 설정하면 kubelet이 헬스체크 API를 날려준다.
- Configmap을 통해 환경변수를 주입하고, 어플리케이션에서 파라미터를 받아서 처리한다.
- 민감한 설정파일은 Secret으로 Volume에 마운트하고 file access 방식으로 접근한다.
- PV를 통해 파일을 worker node에도 저장한다.
위 정보들은 나같은 쿠버네티스 초심자들이 클러스터 상에서 어플리케이션을 배포하고 동작할 때 핵심적으로 생각하고 다루는 내용들이다.
여기에 우리는 이번 포스팅에서 두 가지 관점에서의 Pod 기능을 추가로 다룰 것이다.
바로 'Pod 정보 수집' 그리고 'Pod 종료'이다.
먼저 Pod 정보 수집에 대해서 간단하게 살펴보자.
Prometheus와 같은 수집 서버는 pod 정보를 조회할 목적으로 API를 날린다.
이 때 Service와 연결된 파드가 여러개일 경우, Service는 파드에 트래픽을 랜덤하게 보내주기 때문에 여러번 보내야 모든 Pod를 식별할 수 있다.
이는 많은 네트워크 트래픽을 유발하며 좋지 않은 구조라고 할 수 있다.
VM은 고정 IP가 정해져 있어 조회 시 필요한 만큼 조회 트래픽을 날릴 수 있다.
반면 Pod는 IP가 생성시 자동으로 할당되고 재생성 되는 경우에 IP가 변경된다. 따라서 내가 원하는 Pod에 트래픽을 날리기 어렵다.
따라서 수집 서버가 pod의 정보를 조회하는 것이 아닌, 역으로 pod가 수집 서버에 자신의 정보를 전송하게 해야 한다.
이를 위해 App 내부에는 내 pod 정보를 조회하는 로직이 필요하며 다음과 같이 세 가지 방식이 존재한다.
- Args
- File
- kube-apiserver
이러한 형태로 설계하게 되면 마이크로서비스 아키텍처에서 전체적인 트래픽을 줄일 수 있다.
다음으로 Pod 종료에 대한 시나리오를 살펴보자.
보통 하나의 app이 하나의 pod 위에서 실행된다. 이 때 파드가 종료되면 실행 중인 app도 종료된다.
app이 종료되는 원인은 매우 다양하며 운영상의 문제나 에러가 발생하여 app이 종료될 경우 해당 로그를 살펴봐야한다.
또한 kubernetes 클러스터 상에서 scaling이 일어나고 새로운 버전의 app을 배포하는 업데이트도 빈번하게 일어나기 때문에 종료 시 로그를 남기는 것이 굉장히 중요하다.
파드가 삭제되는 경우에 다음 프로세스가 수행된다.
- pod 삭제
- kubelet이 종료 신호를 보냄
- app이 스스로 종료될 때 까지 대기.
- 일정 시간 이후에도 삭제되지 않으면 강제 삭제
이 때 app이 종료될 때 어떠한 정리를 해야 하는지 정의하는 것이 Graceful Shutdown이다.
또한 memory leak이나 장애 발생 시 app이 재시작되는데, 이는 운영상으로 치명적이기 때문에 에러 사유를 저장해야 하고 pod에서 kubectl로 사유를 조회할 수 있다.
파드의 상태 phase는 다음과 같다.
- 파드 생성 중: Pending
- 파드 기동/운영: Running
- 파드 종료: Succeede / Failed
내부 컨테이너 상태는 ContainerStatuses로 나타나며 다음과 같다.
- waiting: CrashLoopBackOff, ContainerCreating
- Running
- Terminated: Completed, Error
내 POD 정보를 API로 노출시키기
pod에서 가져올 수 있는 정보에는 크게 metadata, spec, status가 있다.
- metadata: name, uid, namespace, labels, annotations
- spec: nodeName, serviceAccountName, containers.resource
- status: hostIP, podIP
쿠버네티스는 보안적인 이유로 가져올 수 있는 정보들을 제한한다.
downwardAPI
pod의 정보를 노출시키는 첫 번째 방법은 downwardAPI이다.
https://kubernetes.io/ko/docs/concepts/workloads/pods/downward-api/
다운워드(Downward) API
실행 중인 컨테이너에 파드 및 컨테이너 필드를 노출하는 두 가지 방법이 있다. 환경 변수를 활용하거나, 그리고 특수한 볼륨 타입으로 채워진 파일을 이용한다. 파드 및 컨테이너 필드를 노출
kubernetes.io
deployment.yaml에서 볼륨을 통해 downwardAPI 속성을 만들 수 있다.
apiVersion: apps/v1
kind: Deployment
template:
spec:
containers:
volumeMounts:
- name: secret-datasource
mountPath: /usr/src/myapp/datasource
- name: downward-api-pod-info
mountPath: /usr/src/myapp/downward-api
volumes:
- name: secret-datasource
secret:
secretName: api-tester-3211-postgresql
- name: downward-api-pod-info
downwardAPI:
items:
- path: "metadata_labels"
fieldRef:
fieldPath: metadata.labels
- path: "metadata_annotations"
fieldRef:
fieldPath: metadata.annotations
spec 하위 volumes 속성에서 downwardAPI를 위와 같이 정의한다.
item으로 각 path의 이름과 fieldPath에 불러올 속성을 정의할 수 있다.
volumeMounts로 컨테이너 내부 폴더 경로를 지정하게 되면 downwardAPI로 정의한 파드 정보를 가진 파일이 생성된다.
해당 파일을 app에서 읽어 현재 app이 띄워진 파드의 정보를 조회할 수 있다.
label이나 annotation 같이 list 형태의 값들이 필요할 때는 volume을 이용해야 하고,
파드 ip나 노드 이름 같은 단일 string 정보는 configmap 환경변수를 이용하는게 편리하다.
env:
- name: downward_env_pod-name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: downward_env_node-name
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: downward_env_pod-ip
valueFrom:
fieldRef:
fieldPath: status.podIP
이런식으로 deployment의 env 속성에서 valueFrom.fieldRef.fieldPath를 사용하면 파드의 정보를 환경변수로 주입할 수 있다.
파드 내부에 접속해서 env로 환경변수를 찍어보면 env의 valueFrom으로 주입한 파드 속성이 나타난다.
bash-4.4# env
downward_env_pod-ip=192.168.13.237
downward_env_node-name=personal
downward_env_pod-name=api-tester-3211-649cc5bc4b-d6vns
또한 volume으로 연결된 파일을 확인해보면 역시 downwardAPI.items로 정의해준 이름의 파일이 생성되어있는걸 확인할 수 있고, 파일 내용 또한 파드 정보를 담고 있는 것을 확인할 수 있다.
bash-4.4# cd /usr/src/myapp/downward-api/
bash-4.4# ls
metadata_annotations metadata_labels
bash-4.4# cat metadata_annotations
cni.projectcalico.org/containerID="93f026723b928dec064c09cd5521be8fca04da796ed1a80014f23f6ec3c1d392"
cni.projectcalico.org/podIP="192.168.13.237/32"
cni.projectcalico.org/podIPs="192.168.13.237/32"
kubectl.kubernetes.io/restartedAt="2025-02-17T20:53:49+09:00"
kubernetes.io/config.seen="2025-02-22T13:36:06.362773668+09:00"
kubernetes.io/config.source="api"
bash-4.4# cat metadata_labels
component="backend-server"
instance="api-tester-3211"
name="api-tester"
part-of="k8s-anotherclass"
pod-template-hash="649cc5bc4b"
version="3.0.0"
kube-apiserver
위에서 downwardAPI를 이용하여 조회한 파드의 name, uid, nodeName, hostIP와 같은 정보들은 정적인 값이다.
반면 파드나 컨테이너의 상태를 나타내는 phase, containerStatuses 같은 값들은 현재 상황에 따라 동적으로 바뀐다.
이러한 값들을 조회하기 위해서는 보안적인 측면이 해결되어야 한다. 이를 위해 kube-apiserver에 app이 API 호출을 날려 파드 상태를 저장하고 있는 etcd database를 조회해야 한다.
하지만 아무리 같은 쿠버네티스 클러스터에 존재하는 pod라고 할지라도 쉽게 kube-apiserver에 접근하지 못한다. 악의적인 공격자가 파드에 접근하여 다른 파드에 공격을 할 수 있기 때문에 보안적인 이유로 차단이 되어있.
ServiceAccount는 pod가 생성될 때 같은 namespace 이름으로 자동으로 세팅된다.
하지만 이 default로 설정된 ServiceAccount로는 app에서 kube-apiserver에 API를 날릴 수 없다.
이 문제를 해결하기 위해 namespace 내부에 Role이라는 object를 만들고 리소스 사용 범위를 설정해준다.
이후 RoleBinding 오브젝트를 생성하여 ServiceAccount에 해당 Role의 권한을 바인딩 해준다.
여기서 추가로 중요한 포인트는 Secret이다.
secret을 token type으로 생성하게 되면 자동으로 namespace, ca.crt, token 값이 만들어진다.
secret의 annotation에 ServiceAccount를 연결해주면 pod가 생성될 때 자동으로 쿠버네티스가 컨테이너 내부 경로에 secret의 데이터를 마운팅 해준다.
해당 경로에 마운팅되는 token, ca.crt, namepsace와 같은 인증정보를 app 내부에서 조회하여 인증 후 kube-apiserver에 API 호출하는 로직을 만들면 파드의 민감한 정보들도 조회할 수 있게된다.
권한을 부여하는 Role, RoleBinding은 같은 namespace 안에서만 권한을 부여한다.
kubernetes-dashboard 처럼 모든 리소스에 대한 조회/생성/삭제 권한을 부여하기 위해서는 ClusterRole 오브젝트로 클러스터 전체 자원에 대한 권한을 설정해야 한다. 마찬가지로 ClusterRoleBinding으로 ServiceAccount에 ClusterRole을 연결해주면 클러스터 전체 자원에 대한 권한을 가지는 Pod를 생성할 수 있다.
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: anotherclass-321
name: api-tester-3211
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: anotherclass-321
name: api-tester-3211
rules:
- apiGroups: ["*"]
resources: ["pods", "services", "deployments"] # 복수로 사용 kubectl api-resources
verbs: ["get", "list"] # "get", "list", "watch", "create", "update", "patch", "delete"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: anotherclass-321
name: api-tester-3211
subjects:
- kind: ServiceAccount
namespace: anotherclass-321
name: api-tester-3211
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: api-tester-3211
이렇게 Role-RoleBinding-ServiceAccount를 정의한다.
Role의 속성 정의는 다음과 같다.
▶ Role 속성
[apiGroups] : [""] -> 코어 그룹, ["apps"] -> 네임드 그룹
● 코어 그룹 : Pod나 Service 등의 기본적인 리소스 (apiVersion상 v1로 표기, API는 /api/v1)
● 네임드 그룹 : Deployment등 특정 기능으로 확장한 리소스 (apiVersion상 app/v1로 표기, API는 /apis/apps/v1)
- apps: Deployment, DaemonSet, StatefulSet 등 애플리케이션을 관리하는 리소스를 포함
- autoscaling: HorizontalPodAutoscaler와 같은 자동 스케일링 관련 리소스를 포함
- batch: Job과 CronJob과 같은 배치 처리 리소스를 포함
- networking.k8s.io: NetworkPolicy와 Ingress와 같은 네트워크 관련 리소스를 포함
- rbac.authorization.k8s.io: Role과 RoleBinding 같은 접근 제어 리소스를 관리
- storage.k8s.io: Volume과 StorageClass 같은 스토리지 리소스를 관리
[resources] : ["pods", "services", "deployment"] -> 복수로 사용 (kubectl api-resources)
[verbs] : [”get", "list"] -> get, list, watch, create, update, patch, delete
이제 deployment의 spec에 서비스 어카운트를 지정해준다.
spec:
serviceAccountName: api-tester-3211
그 후 secret을 통해 파드에 인증 정보를 마운팅한다.
apiVersion: v1
kind: Secret
metadata:
namespace: anotherclass-321
name: api-tester-3211-token
annotations:
kubernetes.io/service-account.name: api-tester-3211
type: kubernetes.io/service-account-token
이렇게 service-account-token type의 secret을 생성하고 annotation에 service-account.name을 지정하면
자동으로 해당 ServiceAccount를 사용하는 pod 내부에 secret 정보들로 파일이 생성된다.
bash-4.4# cd /var/run/secrets/kubernetes.io/serviceaccount/
bash-4.4# ls
ca.crt namespace token
아래와 같이 Spring App 내부에서 ServiceAccount 인증 파일을 읽어 권한을 얻은 후
kubernetes ApiClient를 생성한 뒤 Kubernetes API를 호출할 수 있다.
public String getSelfPodKubeApiServer(String clusterUrl, String podName, String path) {
// 토큰과 CA 인증서 경로 설정
String tokenPath = path + "token";
String caPath = path + "ca.crt";
String namespacePath = path + "namespace";
String responseString = "";
log.info(clusterUrl);
try {
// 파일에서 Token 읽기
String token = new String(Files.readAllBytes(Paths.get(tokenPath)));
String namespace = new String(Files.readAllBytes(Paths.get(namespacePath)));
log.info("token: " + token);
log.info("caPath: " + caPath);
log.info("namespace: " + namespace);
log.info("podName: " + podName);
// ApiClient 생성 및 설정
ApiClient client = Config.defaultClient();
client.setBasePath(clusterUrl);
client.setApiKey("Bearer " + token);
client.setSslCaCert(new java.io.FileInputStream(caPath));
Configuration.setDefaultApiClient(client);
// Kubernetes API 호출
CoreV1Api api = new CoreV1Api();
V1Pod pod = api.readNamespacedPod(podName, namespace, "true");
responseString = Yaml.dump(pod);
log.info("responseString: " + responseString);
} catch (ApiException e) {
log.error("Status: " + e.getCode());
log.error("Body: " + e.getResponseBody());
responseString = e.getResponseBody();
} catch (Exception e) {
e.printStackTrace();
}
return responseString;
}
받은 정보의 일부를 출력해보면, 다음과 같이 파드와 컨테이너의 현재 상태를 확인할 수 있다.
status:
conditions:
- type: Initialized
lastTransitionTime: '2025-02-17T11:55:46Z'
status: 'True'
- type: Ready
lastTransitionTime: '2025-02-22T04:40:41Z'
status: 'True'
- type: ContainersReady
lastTransitionTime: '2025-02-22T04:40:41Z'
status: 'True'
- type: PodScheduled
lastTransitionTime: '2025-02-17T11:55:46Z'
status: 'True'
containerStatuses:
- containerID: containerd://fb27e6763205f327d3c5ba1cf2c7c055fe854bc1f34e31584a190e2b4c611865
image: docker.io/1pro/api-tester:3.0.0
imageID: docker.io/1pro/api-tester@sha256:d6c2f4bef111171f82318cc0362d816281bcafb42e117ff2642baa5b652b9b66
lastState:
terminated:
containerID: containerd://1969d349a0370ff78882b3ece88f52c1138ee6e703081cd4887dc5790d78e2a1
exitCode: 255
finishedAt: '2025-02-22T04:35:46Z'
reason: Unknown
startedAt: '2025-02-17T11:57:13Z'
name: api-tester-3211
ready: true
restartCount: 1
started: true
state:
running:
startedAt: '2025-02-22T04:39:54Z'
hostIP: 10.0.2.15
phase: Running
'인프라' 카테고리의 다른 글
[쿠버네티스] 인프라 구성으로 배우는 Kubernetes Service의 거의 모든 기능들 (0) | 2025.03.09 |
---|---|
[쿠버네티스] Application 개발자가 꼭 알아야하는 Kubernetes Pod 기능 (2) - Pod 종료 시 안정적으로 Application 종료하기 (0) | 2025.03.08 |
개발자 쿠버네티스 개발/테스트 환경 구축하기 (0) | 2025.03.07 |
쿠버네티스에서의 컨테이너, 가상화 기술 정리 (0) | 2025.03.04 |
[Docker] Dockerfile 명령어 및 작성예시 (1) | 2024.12.17 |