pod가 만들어지기 전의 데이터들, 가령 pod이름, pod가 배치된 node, pod-ip, label, annotation들을 어떻게 알 수 있을까??
이런 데이터에 접근할 수 있도록 하는 것이 바로 kubernetes의 downward API이다. downwared API는 REST API처럼 요청하는 endpoint기반의 query가 아니라 pod생성 시에 환경변수나 file로 필요한 metadata를 전달하는 방식이다.
다음의 그림을 보도록 하자.
위 그림과 같이 API server가 downward API volume을 이용해 pod에 관한 metadata를 넘겨주거나, 환경변수로 정보를 넘겨준다.
현재까지는 다음의 정보들이 container에 들어온다.
1. pod이름
2. pod ip주소
3. pod가 속한 namespace
4. pod가 동작하고 있는 node이름
5. pod가 동작하고 있는 service account의 이름
6. 각 container마다의 CPU, memory 요청량
7. 각 container마다의 CPU, memroy 제한량
8. pod label과 annotation
pod label과 annotation은 downward API volume을 통해서만 전달되고, 나머지는 환경변수나 downwardAPI volume 둘 중 하나를 통해 전달된다.
먼저 환경변수를 통해 metadata를 전달하도록 해보자. pod description을 통해서 특정 pod metadata를 전달할 수 있다.
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: 4Mi
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: 1K
pod name, pod namespace, node name, service account 등과 같은 metadata, status는 모두 fieldRef
를 통해서 얻을 수 있다.
반면에 CPU, memory 요청량, 제한량 등은 resourceFieldRef
를 통해서 얻을 수 있고, divisor
를 통해서 단위를 설정할 수 있다. 즉, 원래 값에 divisor
만큼 나누어서 단위를 맞추는 것이다.
다음의 그림은 container에 환경변수로 저장된 metadata, status가 어디서부터 온 것이고 설정된 것인지 나타낸 그림이다.
다음은 실제로 배포된 pod에 해당 환경변수가 할당되었는 지 확인하는 코드이다.
kubectl exec downward -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=downward
SERVICE_ACCOUNT=default
CONTAINER_CPU_REQUEST_MILLICORES=15
CONTAINER_MEMORY_LIMIT_KIBIBYTES=4096
POD_NAME=downward
POD_NAMESPACE=default
POD_IP=10.244.219.193
NODE_NAME=ubuntu20
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
잘 적용된 것을 볼 수 있다. 이제 모든 container에서 pod에 설정한 metadata, status의 환경변수를 가져올 수 있게 된 것이다.
다음으로 metadata를 downwardAPI volume을 통해 전달해보도록 하자.
downwardAPI volume을 먼저 정의한다음, pod에 volume mount를 시켜주면 된다. pod의 label이나 annotation들은 downwardAPI volume을 통해서만 file로만 접근이 가능하기 때문에, 필요하다면 반드시 설정해주어야 한다.
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
volume
부분을 보면 downward
라는 volume을 만들도록 한다. 그리고 downwardAPI
아래로 items
를 나열하는데 여기에 path
에 적힌 이름들이 파일이름이 된다. 가령 podName
은 podName
file이 되는 것이다.
downward
volume은 volumeMounts
를 통해서 /etc/downward
path로 마운팅되는데, podName
파일의 경우는 /etc/downward/podName
에 파일로 저장되고, labels
의 경우는 /etc/downward/labels
파일에 저장되어, container에서 file로 접근이 가능하다.
재밌는 것은 resourceFieldRef
는 containerName
를 가지는 데, 이는 pod name과 달리 container마다 다르기 때문이다. 즉, pod name, namespace, labels, annotation들은 pod안에 모든 container들이 공유하는 것이지만 cpu requests, memory request, cpu limit, memory limit 등은 container 개별적으로 유의미한 값이기 때문에 container level로 파악하기 위해서 containerName
을 적어야한다.
다음은 main
container가 downwardAPI volume을 어떻게 접근하고 있고, 해당 file들이 어디서부터 설정되었는 지를 보여주는 그림이다.
이전에 만들었던 downward
pod는 donw시키고 위의 yaml으로 새 pod를 만들도록 하자.
우리가 원하는 대로 파일들이 제대로 만들어졌는 지 확인해보도록 하자.
kubectl exec downward -- ls -lL /etc/downward
total 24
-rw-r--r-- 1 root root 337 Dec 7 13:21 annotations
-rw-r--r-- 1 root root 2 Dec 7 13:21 containerCpuRequestMilliCores
-rw-r--r-- 1 root root 7 Dec 7 13:21 containerMemoryLimitBytes
-rw-r--r-- 1 root root 9 Dec 7 13:21 labels
-rw-r--r-- 1 root root 8 Dec 7 13:21 podName
-rw-r--r-- 1 root root 7 Dec 7 13:21 podNamespace
annotations
, labels
가 있는 것을 알 수 있다.
이제 cat
을 통해서 제대로 파일에 원하는 값이 써있는 지 보도록 하자.
kubectl exec downward -- cat /etc/downward/labels
foo="bar"
kubectl exec downward -- cat /etc/downward/annotations
...
key1="value1"
key2="multi\nline\nvalue\n"
kubernetes.io/config.seen="2023-12-19T22:48:14.051738329+09:00"
kubernetes.io/config.source="api"
key
, value
쌍으로 metadata들이 저장된 것을 확인할 수 있다.
annotation
과 label
은 pod가 동작중에 변경될 수 있는데, 만약 변경된다면 kubernetes api server가 이를 알아채서 pod의 label, annotation 정보를 담고 있는 file을 새로 업데이트한다. 즉, 우리의 경우는 downward
pod의 /etc/downward/labels
와 /etc/downward/annotations
의 정보가 업데이트 된다는 것이다.
이 때문에 label과 annotation을 환경변수로 넣지 못하는 것이다. 왜냐하면 환경변수에서는 새로 업데이트된 label과 annotation을 반영하지 못하기 때문이다.
downward API를 통해서 일부 pod의 metadata들을 가져올 수 있음을 알 수 있다. 문제는, 이는 굉장히 한정적인 data만 가져올 수 있다는 것인데, 더욱 더 많은 data를 가져오고 싶다면 kubernetes api server에 직접 요구하는 수 밖에 없다.
직접 REST API로 kubernetes api server에 정보를 요청하는 방법이다. 이전에 downward API를 통해서 정보를 받는 것은 하나의 pod안의 정보만 가능한 것이다. 즉, 다른 pod나 다른 resource(service, secret etc)을 통해 정보를 받는 것은 불가능하다.
이를 가능하게 해주는 것이 바로 kubernetes api server
에 요청을 보내는 것이다.
다음과 같이 직접 kubernetes api server
에 REST API를 보내어 metadata를 얻어내는 것이다.
먼저, api server의 URL을 얻기위해서는 다음과 같이 할 수 있따.
ubectl cluster-info
Kubernetes control plane is running at https://116.121.222.205:6443
CoreDNS is running at https://116.121.222.205:6443/api/v1/
https://116.121.222.205:6443
가 바로 kubernetes api server의 endpoint이다.
문제는 HTTPS
이기도하고 인증이 필요하기도 해서 다음과 같이 일반적인 curl
요청으로는 응답이 오지 않는다.
rl https://116.121.222.205:6443 -k
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
다행이게도 kubernetes에서는 kubernetes api server
에 쉽게 접근할 수 있도록 porxy를 제공해주는데, kubectl proxy
를 사용하면 된다.
kubectl proxy
를 사용하면 인증정보와 같이 피곤한 일들을 자동으로 처리해주어 local에서 요청을 보낼 수 있도록 한다. 이는 또한 중간 매개체로 proxing을 하는 것이 아니라, 직접 API server에 요청을 보내는 것이다.
즉, local server를 켜서 kubernetes api server에 bypass로 요청을 보내는 것이다.
kubectl proxy
Starting to serve on 127.0.0.1:8001
8001
port로 porxy server가 열린 것을 알 수 있다. 이제 요청을 보내보도록 하자.
url localhost:8001
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
...
엄청나게 많은 path들을 볼 수 있을 것이다.
cluster에 배포된 모든 job들의 정보를 요구해보도록 하자. 현재 job이 없기 때문에 아무런 정보가 나오지 않을 것이다.
curl http://localhost:8001/apis/batch/v1/jobs
{
"kind": "JobList",
"apiVersion": "batch/v1",
"metadata": {
"resourceVersion": "4775"
},
"items": []
}
다음과 같이 응답이 온 것을 볼 수 있다. 이를 통해 kubernetes api server
와 통신 중인 것을 알 수 있다.
이렇게 kubernetes cluster에서 api server에 요청을 보낼 수도 있지만, pod 내부에서도 가능하다. 물론 proxy가 없기 때문에 과정이 약간 복잡하다는 문제가 있다.
현재 실행 중인 임의의 pod에 들어가면 다음의 환경변수가 존재한다.
env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
이는 kubernetes api server의 service정보로 해당 URL와 port를 통해서 요청을 보낼 수 있다. 단, 이를 위해서는 SSL과 인증 token을 받아야 하는데, 상당히 번거로우며 추천하지 않는다. pod에서 kubernetes api server에 접근할 수 있는 인증은 pod내부에서 token을 받아오는 방법도 있지만 RBAC
을 사용하는 것이 좋다. RBAC
에 대해서는 추후에 알아보도록 하자.
이러한 방법들은 너무 복잡하고, REST API 명세가 달라지면 요청하는 방법이 달라질 수 있다는 문제가 있다.
따라서, 위의 모든 방법은 추천하지 않으며 그냥 이러한 방법이 있다고만 알도록 하자. 만약 kubernetes api server와 통신할 일이 있다면 kubernetes golang client를 사용하여 통신하기를 바란다. 이유는 단순한데, 가장 업데이트가 잘되고 api server와의 의존성을 없앨 수 있기 때문이다.
만약 pod가 golang으로 이루어있지 않아도 RBAC
로 인증이 완료된 go proxy서버를 만들어 kubernetes api server에 요청을 대신 보내도록 하면 된다.