Kubernetes 의 Downward API

Kubernetes 의 Downward API

쿠버네티스의 파드 내에서 파드의 매니페스트나 속성에 대한 정보를 얻기위한 방법인 Downward API 에 대해 알아보자.

Downward API 란?

애플리케이션이 실행되기 전에 이미 알고있는 속성이나 설정 값들은 ConfigMap 이나 Secret 으로 파드에 전달할 수 있지만,

파드의 이름, 파드의 IP, 파드가 실행되는 노드의 이름 등 실제로 파드가 생성 및 실행이 되기전에는 알 수 없는 속성들도 존재한다.

물론 파드의 레이블이나 어노테이션과 같은 일부 속성들은 파드 생성 이전에도 알 수 있지만,

파드 내에서 정보를 사용하고 싶다는 이유로 이미 설정되어 있는 속성을 ConfigMap 등을 통해 중복하여 정의하고 싶지는 않을 것이다.

이런 속성들을 컨테이너에서 실행 중인 애플리케이션에서 알아내려면 어떻게 해야할까?

이 때 사용되는 것이 Downward API 이다.

Downward API 는 단순히 환경변수, 또는 파일(downwardAPI 볼륨을 통해) 로 위와 같은 속성들을 컨테이너에서 손쉽게 사용할 수 있도록 하는 기능일 뿐이다.

Downward API 를 통해 전달할 수 있는 정보는 다음과 같다.

  • 파드의 이름
  • 파드의 IP 주소
  • 파드가 속한 네임스페이스
  • 파드가 실행중인 노드의 이름
  • 파드가 실행 중인 서비스 어카운트 이름
  • 각 컨테이너의 CPU와 메모리 request
  • 각 컨테이너의 CPU와 메모리 limit
  • 파드의 label
  • 파드의 annotation

참고로, 네임스페이스 정보를 얻기 위해서는 굳이 Downward API 를 사용할 필요도 없다.

k8s 에서는 파드가 API server 와 통신할 수 있도록 하기 위해

각 파드마다 기본적으로 Default token 시크릿 볼륨을 만들어

파드 내 컨테이너의 /var/run/secrets/kubernetes.io/serviceaccount/에 마운트해 주는데,

이 곳에 namespace 라는 파일에 네임스페이스가 적혀있기 때문이다.

환경 변수로 전달하기 vs 볼륨으로 전달하기

Downward API 를 통해 데이터를 전달하기 위한 방법으로는

환경 변수를 통한 방법과 볼륨을 통한 방법, 이렇게 크게 두가지가 있다.

대부분의 경우 환경변수를 통한 방법과 볼륨을 통한 방법 중 어떤 방법을 사용해도 크게 문제가 없지만

약간의 차이점이 있다. 우선 일부 정보들은 둘 중 한가지 방법으로만 얻을 수 있다.

예를 들어 Pod 의 label 과 annotation 은 downwardAPI 볼륨을 통해서만 전달할 수 있다.

그 이유는, Pod 의 label 과 annotation 은 Pod 가 실행되는 동안 수정될 수가 있는데,

이 때 Pod 가 변경된 데이터를 볼 수 있도록 해야 한다.

하지만 환경변수는 컨테이너가 생성된 이후에 외부에서 변경할 수 있는 방법이 없기 때문이다.

반면, 파드가 실행중인 노드의 이름과 IP 는 환경 변수를 통한 방법으로만 얻을 수 있다.

각 방법을 통해 얻을 수 있는 정보들의 전체 목록은 공식 문서를 통해 확인할 수 있다.

다음으로, 각 컨테이너가 가지는 속성인 컨테이너의 리소스 request 와 limit 의 경우

환경변수를 통한 방식으로는 다른 컨테이너의 리소스 정보를 사용할 수가 없지만,

볼륨을 통한 방식으로는 다른 컨테이너의 리소스 정보도 사용할 수가 있다.

왜냐하면 애초에 컨테이너 마다 정의해야 하는 환경변수(env 속성)와는 달리

볼륨은 파드 단위로 정의하기 때문이다(spec.volumes 속성을 통해)

그렇기 때문에 볼륨에서 item 을 정의할 때는 container 를 명시하게 되는데

이 때문에 다른 컨테이너의 리소스 정보도 사용할 수 있는 것이다.

환경 변수로 전달하기

다음 매니페스트 파일로 파드를 생성해보자.

apiVersion: v1
kind: Pod
metadata:
name: downward-env
spec:
containers:
- name: main
image: busybox
command: ["sleep", "99999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 20Mi
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: CONTAINER_CPU_REQUEST_MILLICORES
valueFrom:
resourceFieldRef:
resource: requests.cpu
divisor: 1m
- name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Ki

위와 같이 환경변수로 파드의 매니페스트 파일에 정의한 값들이 설정되어있는 것을 확인할 수 있다.

볼륨으로 전달하기

다음 매니페스트 파일로 파드를 생성해보자.

apiVersion: v1
kind: Pod
metadata:
name: downward-volume
labels:
foo: bar
annotations:
key1: value1
key2: |
multi
line
value
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 40Mi
volumeMounts:
- name: downward
mountPath: /etc/downward
volumes:
- name: downward
downwardAPI:
items:
- path: "podName"
fieldRef:
fieldPath: metadata.name
- path: "podNamespace"
fieldRef:
fieldPath: metadata.namespace
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
- path: "containerCpuRequestMilliCores"
resourceFieldRef:
containerName: main
resource: requests.cpu
divisor: 1m
- path: "containerMemoryLimitBytes"
resourceFieldRef:
containerName: main
resource: limits.memory
divisor: 1

위와 같이 /etc/downward에 볼륨이 잘 마운트되어 매니페스트 파일에서 정의한 필드들이 파일로 세팅되어있는 것을 확인할 수 있다.

Downward API 의 한계

Downward API 는 특정 파드에서 자신의 메타데이터 및 속성 정보를 얻을 수 있게 해주는 기능이다.

그런데 만약 다른 파드, 혹은 파드가 아닌 다른 리소스(예를 들면 job)의 정보가 필요한 경우에는 Downward API 가 도움이 되지 않는다.

이런 정보는 서비스 관련 환경변수나 DNS 로 얻거나, API Server 와 직접 통신해야 한다.

API 서버와 통신하기

API 서버와 통신하기 위해서는 우선 API 서버의 IP 와 포트를 알아야 한다.

쿠버네티스에서는 default 네임스페이스의 경우 kubernetes 라는 이름의 서비스가 자동으로 노출되고 API 서버를 가리키도록 되어있다.

고로, kubectl get svc kubernetes 명령어를 실행하면 API 서버의 IP 와 포트를 알 수 있다.

또한, 파드에는 각 서비스에 대한 정보가 환경변수로 세팅되어 있으므로,

파드 내에서 env | grep KUBERNETES_SERVICE 명령어를 실행해도 kubernetes 서비스, 즉 API Server 의 IP 와 PORT 를 알 수 있다.

이제 IP 와 포트를 알았으니, 앞서 잠깐 언급했던 default token 시크릿 볼륨에 있는 token 과 ca.crt 를 사용하여 API 서버에 정보를 요청할 수 있다.

다음 명령어는 API 서버에 현재 네임스페이스 내에 있는 모든 파드의 목록을 요청하는 명령어다.

$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
$ NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
$ curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods

아마 대부분의 경우 위의 명령어를 수행했을 때 액세스 권한이 없다는 응답이 올 것이다. 이는 RBAC(Role-Based Access Control) 라는 것이 활성화되어 있기 때문이다.

테스트 목적이라면 다음의 명령어를 수행하여 cluster-admin ClusterRole 를 통해 모든 파드에 API 서버에 대한 모든 권한을 부여할 수 있다.

RBAC(Role-Based Access Control) 와 ServiceAccount, Role, ClusterRole, ClusterRoleBinding 에 대해서는 추후 다른 포스트에서 다룰 예정이다.

$ kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --group=system:serviceaccounts

GET 요청 대신 PUT 이나 PATCH 를 통해 리소스를 업데이트 하는 등 CRUD 작업을 모두 수행할 수 있다.

앰배서더 컨테이너를 두어, 애플리케이션에서는 아무런 헤더 없이 단순히 http 요청을 하면

인증서와 토큰 세팅과같은 번거로운 과정을 앰배서더 컨테이너가 대신하도록 할 수도 있다.

참고

Comments