2021. 12. 12. 19:56ㆍDevOps/Docker & Kubernetes
Kubernetes in Action 2nd Edition을 정리한 글입니다.
Passing metadata through the Downward API
애플리케이션은 종종 자신이 동작하고 있는 환경에 대한 정보를 알아야 할 때가 있습니다. 실제로 대부분의 애플리케이션이 Dev, Staging, Production등의 환경을 구분해서 동작하며, 각각의 환경에 따라 서로 다른 정보들을 필요로 합니다. 이를 위해 지난 포스팅에서 살펴보았던 ConfigMap, Secret을 사용하는 방법도 있지만 쿠버네티스에서 제공하는 다른 기능인 DownwardAPI를 사용할 수 도 있습니다. 이번 포스팅에서는 DownwardAPI를 사용하여 running Pod에 Environment Variable에 대한 정보를 제공하는 방법을 살펴보겠습니다.
Understanding the available metadata
애플리케이션의 config data는 pod 생성 전에 결정되기 때문에 environment variable, configMap, secretVolume을 사용해서 전달 할 수 있습니다. 하지만 이러한 환경정보 이외에 파드가 생성되어야만 알 수 있는 정보들도 존재합니다.
이를테면 Pod IP, Pod의 이름, Pod가 실행중인 노드의 이름은 Pod가 생성되고 난 후에 알 수 있기 때문에 이런 정보를 얻는 방법이 필요하며, 이를 위해 Downward API가 존재합니다. 이 Downward API를 사용하면 Pod에 대한 정보, 실행 환경에 대한 정보를 얻을 수 있으며 이는 환경 변수의 형태로 전달되거나 Downward API 볼륨을 이용해서 파일로 전달할 수도 있습니다.
한 가지 주목할 만한 점은 API라는 표현을 사용했지만, Downward API를 사용한다고 했을 때, REST API와 같이 원격 서버에 요청하는 방식은 아니라는 것입니다. Downward API를 사용하면 아래 그림에서와 같이 Pod내에 downwardAPI Volume을 생성하여 그 안에 필요한 metadata들을 파일의 형태로 저장하거나, 컨테이너 내에 환경변수의 형태로 저장하게 됩니다.
Downward API를 사용하면 다음의 정보들을 컨테이너에 넣어줄 수 있습니다.
- Pod의 이름, Pod의 Ip주소
- Pod의 namespace
- Pod가 실행되는 노드의 환경
- Pod가 실행되는 service account (pod가 API Server와 통신시에 인증을 위해 사용되는 계정)
- 컨테이너의 CPU, Memory정보
- Pod의 label과 annotation
이중 pod의 Lable과 annotation은 Pod가 생성된 이후에 변경될 수 있는 정보들이므로 Environment Variable의 형태가 아닌 downwardAPI Volume안의 파일의 형태로만 저장할 수 있습니다. (Environment Variable은 초기화후 파드 종료 전까지 변경 불가)
Exposing metadata through environment variables
컨테이너에서 metadata를 environment variable의 형태로 가져오기 위해서는 아래 yaml파일의 env 아래에 다음과 같이 name, valueFrom, fieldRef, fieldPath를 명시해야 합니다. DownwardAPI를 통해 Pod의 manifest로부터 직접 가져오는 것이기 때문에 value props에 absolute value를 넣는 것이 아닌 ref의 형태로 넣어주게 됩니다.
apiVersion: v1
kind: Pod
metadata:
name: downward
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 6Mi
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
Passing metadata throught files in downwardAPI volume
환경 변수 방식 대신에 직접 파일을 통해 환경 변수를 넣어주고 싶은 경우, downwardAPI Volume을 사용하면 됩니다. 위에서 간단하게 언급했듯, downward API 파일을 가져올 volume을 mount하고 아래와 같이 volume아래의 downwardAPI 필드를 통해 가져오려는 값들을 선언하는 방식으로 사용하면 됩니다.
apiVersion: v1
kind: Pod
metadata:
name: downward
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: 4Mi
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
Updating Labels and Annotations
Lable과 Annotation은 파드가 생성된 이후에 변경이 가능합니다. 환경변수로 expose할 경우, 파드가 재시작되지 않는 이상은 해당 값의 변경을 갱신해줄 방법이 없기 때문에 쿠버네티스에서는 lable과 annotation을 환경변수로 expose하지 못하도록 합니다. 따라서 해당 값을 사용하기 위해서는 downwardAPI Volume을 사용해야 합니다. Volume을 사용하게 되면 값 변경시 파일의 내용이 변경되게 되며, 이를 참조하는 컨테이너에서 갱신된 값을 사용할 수 있게 됩니다.
Referring to Container-Level Metadata in the Volume Specification
환경변수와는 다르게 volume을 사용하는 경우 containerName필드를 추가로 제공해야 합니다. 이전 Volume 포스팅에서 살펴보았듯, kubernetes volume은 파드 레벨에서 사용하는 리소스이기 때문에 특정 컨테이너가 파드 레벨의 volume을 사용할 경우 어떤 컨테이너의 metadata를 사용하는지를 명시해야 하기 때문입니다. 파드 전체에서 사용되는 값, podName, podIp등을 가져올때는 containerName을 명시할 필요는 없고, 컨테이너를 정확하게 식별한 후 가져와야 하는 경우에만 표시해주면 됩니다.
Understanding when to use the Downward API
Downward API를 사용하는 것은 어렵지 않고, shell script등을 통해 환경변수를 설정하는 데서 오는 수고로움을 덜어줍니다. 또한 Application이 쿠버네티스에 의존하지 않도록 도와줍니다.
Talking to the Kubernetes API Server
Downward API는 특정한 Pod와 Container의 Metadata를 손쉽게 가져올 수 있는 방법을 제공하지만, 때때로 다른 파드들과 클러스터 내에 정의된 다른 리소스들에 대한 접근이 필요할 때가 있습니다. Kubernetes API Server와 직접 통신하는 방법을 사용하면 이러한 정보들에 접근하는 것이 가능합니다.
Exploring the Kubernetes REST API
kubectl cluster-info 명령어를 통해 현재 로컬 머신에서 동작하고 있는 Kubernetes API Server의 URL을 알아낼 수 있습니다. 하지만 API Server는 HTTPS를 사용하기 때문에 인증 없이 직접 curl 명령어를 통해서 요청을 보낼 수 없습니다. 따라서 직접 인증을 처리하는 대신 kubectl proxy 명령어를 사용해서 프록시를 통해 API Server에 직접 요청을 보내는 방법을 사용해야 합니다.
Exploring the Kubernetes API through the Kubectl Proxy
kubectl proxy를 사용하면 로컬에서 HTTP 요청을 받아서 Kubernetes API 서버로 요청을 전달합니다. 이때 인증 과정도 proxy가 알아서 처리해주기 때문에 별다른 인증과정 없이 해당 proxy를 통해서 API 서버로 요청을 전달할 수 있습니다. 아래 그림과 같이 root path로 요청을 보내면 path들이 리턴되는데 각각의 path는 리소스를 만들때 사용했던 apiVersion, apiGroup에 대응됩니다.
Exploring the Batch API group's REST endpoint
apis/batch path로 요청을 보내면 다음과 같이 batch API Group의 description과 preferred version등에 대한 정보를 얻을 수 있습니다. 해당 정보에 따르면 batch/v1이 preferred group version인 것을 알 수 있습니다.
preferred version에 따라 apis/batch/v1으로 curl을 해보면 리소스 종류의 목록과 Rest API를 리턴합니다. 여기 안에 리소스들의 이름과 내용들이 명시되어 있으며 apis/batch/v1/jobs로 curl을 해보면 현재 돌아가고 있는 job들의 item 목록을 배열로 받을 수 있습니다.
위에서 살펴본 바와 같이 API Server는 API Group내의 리소스 타입들의 배열들, 그리고 REST Endpoint들을 알려줍니다. 리턴되는 리소스들은 사용자가 쿼리할 수 있는 여러가지 정보들을 담고 있는데 name을 통해 endpoint를, verbs를 통해 CRUD등의 지원하는 요청 방식을 알려줍니다.
Talking to the API Server from within a pod
앞서 Kubernetes API에 요청을 보낼 때는 kubectl proxy를 사용해서 인증을 처리했습니다. 하지만 Pod 내부에는 Kubectl 명령어를 사용할 수 없기 때문에 API 서버와 통신하기 위해서는 인증에 대한 고려가 필요합니다. 이를 위해서 다음 3가지 요소에 대해 고려해야 합니다.
- API 서버의 URL
- 진짜 API 서버와 통신하고 있는지 검증하기 (authenticate)
- API 서버에 인증하기
Running a Pod to try out communication with the API Server
우선 API Server와 통신할 파드를 생성해서 위의 3가지 방식들을 적용해보도록 하겠습니다. 다음과 같이 아무것도 없는 Pod를 생성하고, 안에서 shell을 실행합니다. Pod 내부에서 curl요청을 할 수 있어야 하므로 curl binary이미지를 가지고 있는 curlimages/curl 이미지를 사용해서 Pod를 생성합니다. (참고로 사용한 Docker Image가 Alpine이라면 /bin/bash를 지원하지 않을 수 있으므로 아래와 같이 /bin/sh 를 사용해야 합니다.)
Finding the API Server's Address
Kubernetes 라는 이름으로 service가 default namespace에서 동작하고 있을 것이기 때문에 kubectl get svc를 통해 쉽게 API 주소를 알아낼 수 있습니다.
파드 내부에서는 API Server의 IP주소와 포트번호를 KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT 환경변수를 통해 접근할 수 있습니다.
이 방법 외에도 Kubernetes 내부 DNS 서버에 entry가 생성되기 때문에 https://kubernetes로 요청을 보내도 됩니다. 하지만 API Server가 돌고 있는 포트 번호를 알아야 하기 때문에 환경 변수에서 찾아보거나 DNS SRV Record를 찾아보아야 합니다.
Verifying the Server's Identity
저번 포스팅에서 Secret을 다룰 때, 컨테이너가 생성될 때 자동으로 마운트되는 default-token-xyz가 있다는 것을 살펴보았습니다. 해당 토큰은 /var/run/secrets/kubernetes.io/serviceaccount/ 디렉토리 하부에 있으며 실제로 아까 생성한 컨테이너에서 해당 디렉토리를 조회해 보면 다음과 같이 3개의 파일이 있는 것을 확인할 수 있습니다.
이중 서버의 인증서를 sign한 CA의 정보를 담고 있는 ca.crt파일에 주목해야 합니다. 이 파일은 Kubernetes API Server(진짜 서버)가 서명한 정보를 담고 있기 때문에 통신하려고 하는 서버가 진짜인지 알기 위해서는 해당 서버의 인증서가 ca.crt로 서명되었는지를 체크하면 됩니다. 다음과 같이 curl 요청을 보냈을 때 403응답이 오면 해당 서버가 진짜 API Server임을 검증한 것입니다. (위의 과정은 인증의 단계를 수행한 것은 아니고 해당 서버가 진짜 인증된 서버인지를 확인하기 위한 절차이므로 403 응답이 오는 것이 맞습니다.)
매번 요청을 보낼 때마다 다음과 같은 검증 과정을 처리해야 하므로 다음과 같이 CURL_CA_BUNDLE 환경 변수를 ca.crt파일의 경로로 설정해주면 위의 긴 검증 command를 입력할 필요가 없습니다.
Authenticating with the API Server
서버 검증을 마쳤으므로 이제 서버에 인증을 해야 하고, 인증을 위해서는 Authentication Token이 필요합니다. 이 토큰은 아까 ca.crt파일이 있었던 곳의 token 파일에 있습니다. 환경변수 TOKEN에 해당 토큰 파일을 넣고 curl 요청을 보내면 다음과 같이 성공적으로 API Server와 통신이되는 것을 확인할 수 있습니다.
'DevOps > Docker & Kubernetes' 카테고리의 다른 글
[Kubernetes in Action] StatefulSets: deploying replicated stateful applications (0) | 2021.12.26 |
---|---|
[Kubernetes in Action] Deployments: Updating Applications Declaratively (0) | 2021.12.19 |
[Kubernetes in Action] ConfigMaps and Secrets (0) | 2021.10.30 |
[Kubernetes in Action] Volumes (0) | 2021.10.09 |
[Kubernetes in Action] Services (0) | 2021.10.02 |