Search
Duplicate
🏜️

8장. 애플리케이션에서 파드 메타데이터와 그 외의 리소스에 액세스하기

다루는 내용
컨테이너의 정보를 전달하기 위해 Downward API 사용
k8s 의 REST API 살펴보기
인증과 서버 검증을 kubectl proxy 에 맡기기
컨테이너 내에서 API 서버에 접근하기
에배서더 컨테이너 패터의 이해
k8s clinet lib 사용
때로, application 이 자신의 상세 정보를 포함해 실행 중인 환경 관련 정보, cluster 내의 다른 component에 관한 정보가 필요할 수 있다.
이러한 정보를 얻는 방법과 이런 resource 를 생성하거나 수정하는 방법을 다룬다.

Downward API 로 meta data 전달 (Passing metadata through the Downward API)

config map, secret
이는 사용자가 데이터를 직접 설정한 경우, 혹은 미리 알고 있는 데이터에 적합한 방법이다.
Downward API
pod IP, 호스트 노드 이름 또는 pod 자체의 이름과 같이 실행 시점까지 알려지지 않은 데이터
pod 의 label 이나 annotation 과 같이 어딘가에 이미 설정된 데이터는 다르다
Downward API 는 app이 호출해서 데이터를 가져오는 REST API 와는 다르다
Downward API 는 환경변수 또는 파일로 pod 의 meta data 를 노출한다.
위 그림과 같이 환경변수 또는 file 에 pod 의 spec 또는 상태값이 채워지게 만든다

사용 가능한 meta data 이해

파드 자체의 meta data 를 해당 파드 내에서 실행중인 process 에 노출시킬 수 있다.
그 항목은 다음과 같다
pod 의 이름
pod 의 IP address
pod 가 속한 namespace
pod 가 실행중인 node 의 이름
pod 가 실행중인 service account 의 이름
12장에서 자세히 다룬다
pod 가 API 서버와 통신할 때 인증하는 계정
각 컨테이너의 CPU 와 memory 요청
각 container 의 CPU 와 memory 제한
volume 으로만 받을 수 있는 것
pod 의 label
pod 의 annotations

container 내부에 있는 process 에 meta data 를 전달하는 예시

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 # pod manifest 의 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
YAML
환경변수에 사용하는 downward API: downward-api-env.yml
process 가 실행되면 spec 에 정의한 모든 환경변수를 볼 수 있다.
환경변수를 이용해 파드의 metadata 속성을 파드에 노출한다
다음의 명령어로 환경변수들을 확인할 수 있다. memory 는 너무적어 실행되지 않아 100M 으로 올렸다
kubectl exec -it downward -- env
YAML

downward API 볼륨에 파일로 메타데이터 전달

pod 의 label 이나 annotation 은 환경변수로 노출이 불가능하기 때문에 volumne 으로 노출해야 한다. (이유는 나중에)
환경변수처럼 metadata 를 프로세스에 노출시키려면 앞의 예제와 같이 필드를 명시적으로 지정해야 한다.
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
YAML
이 레이블과 annotation 은 downward API 볼륨으로 노출된다.
볼륨은 /etc/downward 아래에 마운트 된다.
볼륨의 이름이 downward 이다.
pod label 은
/etc/downward/lables
pod annotation 은
/etc/downward/annoation
환경변수로 전달하는 대신 /etc/downward/ 에 볼륨을 정의하고 container 에 마운트한다
이 볼륨에 포함된 파일들은 volume spec 의 downwardAPI.items 속성 아래에 설정한다
컨테이너에 metadata 를 전달하기 위해 downwardAPI 볼륨 사용
kubectl exec downward -- ls 1L /etc/downward
YAML
명령어를 입력해 mount 된 파일을 보자
책에서 입력한 명령어는 deprecated 되어서 이 명령어로 접근을 권장한다
들어가서 살펴보면 앞서 환경변수로 보여줬던 값이 file 로 나온것을 확인할 수 있다
kubectl exec downward -- cat /etc/downward/annotations
YAML
key=value 형식이고 값이 여러개줄인 경우 \n 을 붙여 한 줄로 기록된다.

Label 과 annotation update

파드가 실행되는 동안 label, annotation 을 수정할 수 있다
환경변수는 나중에 업데이트가 안되기 때문에(할 수 있지 않나?) 파일로 노출시키는 것이다

볼륨 스펙에서 컨테이너 수준의 metadata 참조

resourceFieldRef 같은 container 수준의 metadata 를 노출하는 경우 리소스 필드를 참조하는 container 이름을 지정해야 한다.
volumes: - name: downward downwardAPI: items: - path: "containerMemoryLimitBytes" resourceFieldRef: containerName: main # container 의 이름 resource: limits.memory divisor: 1
YAML
resourceFieldRef 컨테이너의 resource limit 또는 요청은 볼륨이 컨테이너가 아니라 pod 수준에서 정의됐다고 생각하면 그 이유가 분명해진다
환경변수를 사용해 노출하는 것 보다 약간 복잡하지만, container 의 resource field 를 다른 container 에 전달할 수 있다는 장점이 있다.

Downward API 사용 시기 이해

downward API 를 사용하면 application 을 다시 만들 필요가 없다
데이터를 가져와 환경변수에 노출하는 shell script 를 만들 필요도 없다
그러나 사용 가능한 meta data 가 제한적이기 때문에 더 많은 정보가 필요하면 다음 절을 참고하라

K8S API 서버와 통신하기

파드에 대한 정보 뿐 아니라 cluster 에 정의된 다른 pod, resource 에 관한 더 많은 정보가 필요할 수 있다.
DNS 를 통해 서비스와 파드에 대한 정보를 얻을 수 있지만, 다른 resource 의 정보가 필요하거나 최신 정보가 필요하다면 API 서버와 직접 통신하는 것을 권장한다
다른 API 오브젝트에 관한 정보를 얻기 위해 pod 내부에서 API 서버와 통신한다
로컬 컴퓨터 서버의 REST endpoint 를 살펴본 후 API 서버와 통신하는 방법을 확인해보자

K8S REST API 살펴보기

만약 k8s 의 API 와 통신하는 application 을 개발한다면 API 에 관해 먼저 알아볼 것이다.
먼저 cluster-info 를 실행해보자
kubectl cluster-info
YAML
서버는 HTTPS 를 사용하기 때문에 curl-k 혹은 --insecure 옵션을 사용해 인증서 확인을 건너뛰자
하지만 인증 때문에 원하는 결과는 얻지 못한다, 이 경우 kubectl proxy 를 실행해 서버와 통신할 수 있다.

kubectl proxy 로 API 서버 access 하기

프록시 서버를 사용해 local 컴퓨터에서 HTTP 연결을 수신 할 수 있다
요청할 때마다 인증 토큰을 전달할 필요가 없게 만든다
각 요청마다 서버의 인증서를 확인하고 중간자가 아닌 실제 API 서버와 통신함을 담보한다
kubectl proxy &
YAML
백그라운드로 실행하지 않으면 새로운 terminal을 띄워야 하는 구조이다.
curl 127.0.0.1:8001 의 결과

kubectl proxy 로 k8s API 살펴보기

반환된 경로 목록은 리소스 정의에 지정한 API 그룹과 버전에 해당한다.
/api/batch/v1 경로의 batch/v1 가 cronjob 리소스의 API 그룹과 버전이란 것을 알아차릴 수 있다
/api/v1 은 일반적인 리소스 pods, services, replicationController 에서 참조하는 v1 이다

배치 API 그룹의 REST endpoint살펴보기

잡 resource 의 경로 뒤에 무엇이 있는지 살펴보자
{ "kind": "APIGroup", "apiVersion": "v1", "name": "batch", "versions": [ { "groupVersion": "batch/v1", "version": "v1" }, { "groupVersion": "batch/v1beta1", "version": "v1beta1" } ], "preferredVersion": { // 선호 "groupVersion": "batch/v1", "version": "v1" } }
JSON
응답에 사용 가능한 버전과 클라이언트가 선호 버전에 관한 정보, batch API 그룹에 대한 설명이 있다.
{ "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "batch/v1", "resources": [ { "name": "jobs", "singularName": "", "namespaced": true, "kind": "Job", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "categories": [ "all" ], "storageVersionHash": "mudhfqk/qZY=" }, { "name": "jobs/status", "singularName": "", "namespaced": true, "kind": "Job", "verbs": [ "get", "patch", "update" ] } ] }
JSON
batch/v1 API 그룹내의 API 리소스 목록
리소스 유형을 담는 배열
네임스페이스 지정 필드가 true 인 job 리소스에 대한 설명
이 리소스와 함께 사용할 동사들
리소스는 상태를 수정하기 위해 특수한 REST endpoint 가 있다.
상태 정보는 검색, 패치, 업데이트할 수 있다.

클러스터에 있는 모든 job instance 나열하기

curl localhost:8001/apis/batch/v1/jobs
JSON
cluster 에 있는 jobs 를 살펴보는 API
{ "kind": "JobList", "apiVersion": "batch/v1", "metadata": { "resourceVersion": "34801" }, "items": [] }
JSON
현재는 아무것도 없다
apiVersion: batch/v1 kind: Job metadata: name: my-job spec: template: metadata: labels: app: batch-job spec: restartPolicy: OnFailure containers: - name: main image: luksa/batch-job
YAML
간단한 job resource 를 실행시키고 위의 API를 다시 호출해보면 다음과 같은 결과를 얻을 수 있다.
위 결과는 아래 명령의 결과와 동일하다
kubectl get job my-job -o json
YAML
결과
명령어를 사용하지 않고 curl 을 이용해도 파드에서 실행되는 application 이 k8s 와 어떻게 통신하는지 이해할만 하다

파드 내에서 API 서버와 통신

kubectl이 없는 pod 내에서 통신하는 방법을 알아보겠다. 단 API 서버와 통신하기 위해 다음의 조건을 만족해야 한다
API 서버의 위치를 찾아야 한다
API 서버와 통신하고 있는지 확인해야 한다
API 서버로 인증해야 한다 그렇지 않으면 볼 수도 없고 아무것도 할 수 없다.

API 서버와의 통신을 시도하기 위해 파드 실행

apiVersion: v1 kind: Pod metadata: name: curl spec: containers: - name: main image: tutum/curl command: ["sleep", "9999999"]
YAML
curl 과 bash 가 있는 image 를 사용해야 한다
kubectl exec -it curl -- bash
YAML
현재 tutum/curl 은 없어졌다

API 서버 주소 찾기

먼저 k8s 서버의 IP 와 port 를 찾아야 한다
kubectl get svc
YAML
5장에서 서비스에 관해 환경변수가 구성돼 있었다.
API 서버의 IP 주소와 container 내부의 KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT 를 찾아내자
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_SERVICE_PORT=443 KUBERNETES_SERVICE_HOST=10.96.0.1
Plain Text
service 마다 DNS 엔트리가 있다
환경변수를 조회할 필요 없이 curl에서 https://kubernetes 를 가리키게 해야 한다.
이 문제를 해결하는 간단한 방법은 -k 를 분여 insecure 로 접근하는 것인데 연결하려는 서버를 맹목적으로 신뢰하기 보단 curl 로 검사해 인증서를 확인하자
💡
서버 인증서 확인을 절대로 건너뛰면 안된다 중간자 공격으로 application 인증 토큰을 공격자에게 노출시킬 수 있다.

사이트 아이덴터티 검증

7장에서 각 건테이너에 마운트 되어 자동으로 생성된 default-token-xyz 시크릿을 살펴보자
k exec -it curl ls /var/run/secrets/kubernetes.io/serviceaccount/
Plain Text
ca.crt namespace token
Plain Text
여기서 인증서에 서명하는데 사용되는 인증기관 CA 인증서에 집중할 것이다.
API 서버와 통신 중인지 확인하려면 서버의 인증서가 CA로 서명되었는지 확인이 필요하다
curl--cacert 옵션과 사용하면 CA 인증서를 지정할 수 있으므로 API 서버를 다시 접속한다
k exec curl -- curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
Plain Text
{ "kind": "Status", "apiVersion": "v1", "metadata": { }, "status": "Failure", "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"", "reason": "Forbidden", "details": { }, "code": 403 }
Plain Text
매번 --cacert /var/run/secrets/kubernetes.io/serviceaccount/ 를 붙이기 귀찮으니 변경하자
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Plain Text
다시 호출해보면 위와 동일한 결과가 나온다
curl https://kubernetes
Plain Text
훨씬 편해졌다

API 서버로 인증

서버에서 인증을 통과해야 cluster 에 배포된 API object 를 읽고, 업데이트하고, 삭제를 할 수 있다.
인증에는 인증 토큰이 필요하다
default-token 시크릿으로 제공되며 시크릿 볼륨의 token 파일에 저장된다.
토큰을 환경 변수에 저장하자
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
Plain Text
하지만...
curl -H "Authorization: Bearer $TOKEN" https://kubernetes
Plain Text
{ "kind": "Status", "apiVersion": "v1", "metadata": { }, "status": "Failure", "message": "forbidden: User \"system:serviceaccount:default:default\" cannot get path \"/\"", "reason": "Forbidden", "details": { }, "code": 403 }
Plain Text
하지만 curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api 로 요청한다면 다음의 결과르는 얻을 수 있다.
{ "kind": "APIVersions", "versions": [ "v1" ], "serverAddressByClientCIDRs": [ { "clientCIDR": "0.0.0.0/0", "serverAddress": "192.168.49.2:8443" } ] }
Plain Text

파드가 실행중인 namespace 얻기

다음의 결과를 봤을때 namespace 가 포함되어 있음을 봤다
ca.crt namespace token
Plain Text
이제 실행중인 파드의 네임스페이스를 얻기 위해 파일을 읽어 환경변수로 로드하고 호출하면서 나열해보자
음... 안된다 뭔가 책이랑 버젼이 달라진거 같다

파드가 k8s 통신하는 방법 정리

application 은 API 와 통신할 때 인증 기관으로부터 서명 됐는지금 검증해야 하며 이것은 ca.cert 에 있다
application 은 token 파일의 내용을 Authorization HTTP header 에 Bearer 토큰으로 넣어 전송하여 자기 자신을 인증해야 한다
namespace 파일은 파드의 API 오프젝트 CRUD 작업을 할 때 API 서버로 전송하는데 사용한다
파드와 API 서버간 통신의 3가지 측면, default-token 시크릿 파일로 API 서버와 통신한다

엠베서더 컨테이너를 이용한 API 서버 통신 간소화

HTTPS, 인증서, 인증 토큰을 다루는 일은 때론 너무 복잡하고 귀찮다. 그래서 서버의 인증서의 유효성 검사를 비활성화 하는 경우도 있다. 하지만, 보안을 유지하면서 통신을 훨씬 간단하게 만드는 방법이 있다
앞서 언급한 kubectl proxy 명령과 같이 파드 내에서도 동일한 방법을 사용하는 것이다

엠베서더 container pattern 소개

API 와 직접 통신하는 것이 아니라 main container 옆에 앰베서더 컨테이너에서 kubectl proxy 를 실행하고 이를 통해 API 와 통신하는 방법이다.
따라서 HTTPS 와 직접 연결하지 않고 HTTP 를 사용하고 엠베서더가 HTTPS 를 사용하는 것이다.
엠베서더를 사용한 API 서버와 연결
파드의 모든 container 는 loof back network interface 를 공유하기 때문에 localhost 를 사용해 이 proxy 에 접근할 수 있다.

추가적인 앰배서더 container 를 사용한 curl 파드 실행

이번에는
apiVersion: v1 kind: Pod metadata: name: curl-with-ambassador spec: containers: - name: main image: tutum/centos command: ["sleep", "9999999"] - name: ambassador image: luksa/kubectl-proxy:1.6.2
YAML
도커 허브에 푸시해둔 kubectl-proxy 이미지를 기반으로 추가적인 앰배서더 컨테이너를 실행한다
직접 필드하고자 한다면 /Chapter08/kubectl-proxy/ 를 참조하면 된다.
kubectl exec -it curl-with-ambassador -c main -- bash
YAML
이제 pod 에 두개의 container 를 띄워두었다. main 에 명령어를 실행하고 싶다면
-c main 이라는 플래그를 추가해야 한다.(단, 첫번째 컨테이너에 실행하고 싶다면 생략가능)

앰배서더를 통한 API 서버와 통신

이 부분은 책에서는 성공했다고 나오지면 내 컴퓨터에서는 성공하지 못했다 container 문제인 것으로 추측된다. 그래서 책의 내용대로 작성한다
제대로 바인딩이 되었다면 curl 을 사용해 localhost:8001 로 접속할수 있다.
정확히 무슨일이 일어났는지는 다음 그림에서 파악해보자
main container 는 HTTP 요청으로 localhost:8001 로 요청한다
같은 pod 이기 때문에 문제 없이 요청이 가능하다
pod 는 proxy 로 요청을 하고 proxy 에서 인증과 관련된 기능(신원확인)을 수행한다
외부 서비스에 연결하는 복잡성을 숨기고 main container 에서 실행되는 application 을 단순화한다
앰배서더 container 는 application 언어와도 상관없이 재사용 할 수 있다.

Client Library 를 사용해 API 서버와 통신

간단한 작업이 아니라 그 이상의 작업을 수행해야 한다면 k8s client 라이브러리 중 하나를 선택하는 것이 좋다.

client library 사용

현재 API Machinery SIG(Special Interest Group) 에서 지원하눈 K8S API clinet 는 두가지가 있다.
공식적인 것 외에도 다양한 것이 잇다
Java client by Fabric8—https://github.com/fabric8io/ku
이 라이브러리들은 HTTPS 를 지원하고 인증을 관리하므로 앰배서더 container 가 필요 없다

Fabric8 Java Client 를 사용한 k8s 상호작용 예시

package kubia; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Arrays; public class Fabric8ClientTest { public static void main(String[] args) throws Exception { KubernetesClient client = new DefaultKubernetesClient(); // list pods in the default namespace PodList pods = client.pods().inNamespace("default").list(); pods.getItems().stream().forEach(s -> System.out.println("Found pod: " + s.getMetadata().getName())); // create a pod System.out.println("Creating a pod"); Pod pod = client.pods().inNamespace("default").createNew() .withNewMetadata() .withName("my-programmatically-created-pod") .endMetadata() .withNewSpec() .addNewContainer() .withName("main") .withImage("busybox") .withCommand(Arrays.asList("sleep", "99999")) .endContainer() .endSpec() .done(); System.out.println("Created pod: " + pod); // edit the pod (add a label to it) client.pods().inNamespace("default").withName("my-programmatically-created-pod").edit() .editMetadata() .addToLabels("foo", "bar") .endMetadata() .done(); System.out.println("Added label foo=bar to pod"); System.out.println("Waiting 1 minute before deleting pod..."); Thread.sleep(60000); // delete the pod client.pods().inNamespace("default").withName("my-programmatically-created-pod").delete(); System.out.println("Deleted the pod"); } }
Java
Fabric8 은 DSL API를 제공하기 때문에 코드 자체적으로 설명되도록 작성해야 한다

스웨거와 OpenAPI를 사용해 자신의 라이브러리 구축

선택한 언어에 사용할 client 가 없다면 Swagger API 를 사용해 client 라이브러리와 문서를 생성할 수 있다
k8s API 서버는 /swaggerapi 에서 스웨거 API 정의를 공개한다
swagger.json 에서 확인할 수 있다

스웨거 UI로 API 살펴보기

아 로컬에 띄워보고 싶은데 ㅠㅠ
minikube start --extra-config=apiserver.Features.EnableSwaggerUI=true
Java
http(s)://<api server>:<port>/swagger-ui
Java
TOP