sungsu9022 / study-kubernetes-in-action

study-kubernetes
12 stars 8 forks source link

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

Open sungsu9022 opened 4 years ago

sungsu9022 commented 4 years ago

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

Downward API사용방법과 쿠버네티스 REST API 사용방법, 인증과 서버 검증을 kubectl proxy에 맡기는 방법, 컨테이너 내에서 API 서버에 접근하는 방법, 앰배서더 컨테이너 패턴의 이해, 쿠버네티스 클라이언트 라이브러리 사용방법 등을 살펴본다.

  • 특정 파드와 컨테이너 메타데이터를 컨테이너로 전달하는 방법과 컨테이너 내에서 실행중인 애플리케이션이 쿠버네티스 API 서버와 통신해 클러스터에 배포된 리소스의 정보를 얻는 것이 얼마나 쉬운지, 이런 리소스를 생하거나 수정하는 방법을 살펴보자.

8.1 Downward API로 메타데이터 전달

스크린샷 2020-08-23 오후 9 27 39

8.1.1 사용 가능한 메타데이터 이해

메타데이터 종류

8.1.2 환경변수로 메타데이터 노출하기

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: 20Mi                            # 메모리 사이즈 4Mi로는 파드가 안뜨고 20으로 올려줘야 정상동작( https://github.com/kubernetes/minikube/issues/6160 )
    env:
    - name: POD_NAME                       # 특정 값을 설정하는 대신 파드 매니페스트의 metadata.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:                     # 컨테이너의 CPU/메모리 요청과 제한은 fieldRef 대신 resourceFieldRef를 사용해 참조
          resource: requests.cpu
          divisor: 1m                              # 리소스 필드의 경우 필요한 단위의 값을 얻으려면 제수(divisor)을 정의한다.
    - name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
          divisor: 1Ki
스크린샷 2020-08-23 오후 9 33 13
# downward API를 사용하는 pod 생성
kubectl create -f downward-api-env.yaml

# pod의 env 조회
kubectl exec downward env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=downward
POD_NAMESPACE=default
POD_IP=172.17.0.4
NODE_NAME=minikube
SERVICE_ACCOUNT=default
CONTAINER_CPU_REQUEST_MILLICORES=15
CONTAINER_MEMORY_LIMIT_KIBIBYTES=20480
POD_NAME=downward
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_PORT=tcp://10.102.194.76:80
KUBIA_PORT_80_TCP_ADDR=10.102.194.76
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_SERVICE_HOST=10.102.194.76
KUBIA_PORT_80_TCP=tcp://10.102.194.76:80
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBIA_PORT_80_TCP_PROTO=tcp
HOME=/root

8.1.3 downwardAPI 보륨에 파일로 메타데이터 전달

apiVersion: v1
kind: Pod
metadata:
  name: downward
  labels:                                     # 이 레이블과 어노테이션은 downwardAPI 볼륨으로 노출된다.
    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: 20Mi
    volumeMounts:                           # downward 볼륨 /etc/downward에 마운트
    - name: downward
      mountPath: /etc/downward
  volumes:
  - name: downward                              # downwardAPI 볼륨 정의
    downwardAPI:
      items:
      - path: "podName"                          #  metadata.name에  정의한 이름은 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
스크린샷 2020-08-23 오후 9 46 32
# pod 생성
kubectl create -f downward-api-volume.yaml

# pod 데이터 확인
kubectl exec downward -- ls -al /etc/downward/
kubectl exec downward -- cat /etc/downward/labels
kubectl exec downward -- cat /etc/downward/annotations

레이블과 어노테이션 업데이트

볼륨 스펙에서 컨테이너 수준의 메타데이터 참조

spec:
  volumes:
  - name: downward
    downwardAPI:
      items:
      - path: "containerCpuRequestMilliCores"
        resourceFieldRef:
          containerName: main              # 컨테이너 이름이 필수로 지정되어야 한다.
          resource: requests.cpu
          divisor: 1m

Downward API 사용 시기 이해

8.2 쿠버네티스 API 서버와 통신하기

8.2.1 쿠버네티스 REST API 살펴보기

# 쿠버네티스 클러스터 정보 조회
kubectl cluster-info

Kubernetes master is running at https://192.168.64.2:8443
KubeDNS is running at https://192.168.64.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

# 호출 ( Forbidden 403 에러)
curl https://192.168.64.2:8443 -k

kubectl proxy로 API 서버 엑세스하기

# proxy 실행
kubectl proxy

# local proxy 호출
curl http://localhost:8001

{
  "paths": [
    "/api",
    "/api/v1",          # 대부분의 리소스 타입을 여기서 확인
    "/apis",
    "/apis/",
    "/apis/admissionregistration.k8s.io",
    "/apis/admissionregistration.k8s.io/v1",
    "/apis/admissionregistration.k8s.io/v1beta1",
    "/apis/apiextensions.k8s.io",
    "/apis/apiextensions.k8s.io/v1",
    "/apis/apiextensions.k8s.io/v1beta1",
    "/apis/apiregistration.k8s.io",
    "/apis/apiregistration.k8s.io/v1",
    "/apis/apiregistration.k8s.io/v1beta1",
    "/apis/apps",
    "/apis/apps/v1",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/autoscaling/v2beta1",
    "/apis/autoscaling/v2beta2",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v1beta1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1beta1",
    "/apis/coordination.k8s.io",
    "/apis/coordination.k8s.io/v1",
    "/apis/coordination.k8s.io/v1beta1",
    "/apis/discovery.k8s.io",
    "/apis/discovery.k8s.io/v1beta1",
    "/apis/events.k8s.io",
    "/apis/events.k8s.io/v1beta1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/networking.k8s.io",
    "/apis/networking.k8s.io/v1",
    "/apis/networking.k8s.io/v1beta1",
    "/apis/node.k8s.io",
    "/apis/node.k8s.io/v1beta1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1",
    "/apis/rbac.authorization.k8s.io/v1beta1",
    "/apis/scheduling.k8s.io",
    "/apis/scheduling.k8s.io/v1",
    "/apis/scheduling.k8s.io/v1beta1",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1",
    "/apis/storage.k8s.io/v1beta1",
    "/healthz",
    "/healthz/autoregister-completion",
    "/healthz/etcd",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/apiservice-openapi-controller",
    "/healthz/poststarthook/apiservice-registration-controller",
    "/healthz/poststarthook/apiservice-status-available-controller",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/crd-informer-synced",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/healthz/poststarthook/kube-apiserver-autoregistration",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/healthz/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/healthz/poststarthook/start-apiextensions-controllers",
    "/healthz/poststarthook/start-apiextensions-informers",
    "/healthz/poststarthook/start-cluster-authentication-info-controller",
    "/healthz/poststarthook/start-kube-aggregator-informers",
    "/healthz/poststarthook/start-kube-apiserver-admission-initializer",
    "/livez",
    "/livez/autoregister-completion",
    "/livez/etcd",
    "/livez/log",
    "/livez/ping",
    "/livez/poststarthook/apiservice-openapi-controller",
    "/livez/poststarthook/apiservice-registration-controller",
    "/livez/poststarthook/apiservice-status-available-controller",
    "/livez/poststarthook/bootstrap-controller",
    "/livez/poststarthook/crd-informer-synced",
    "/livez/poststarthook/generic-apiserver-start-informers",
    "/livez/poststarthook/kube-apiserver-autoregistration",
    "/livez/poststarthook/rbac/bootstrap-roles",
    "/livez/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/livez/poststarthook/start-apiextensions-controllers",
    "/livez/poststarthook/start-apiextensions-informers",
    "/livez/poststarthook/start-cluster-authentication-info-controller",
    "/livez/poststarthook/start-kube-aggregator-informers",
    "/livez/poststarthook/start-kube-apiserver-admission-initializer",
    "/logs",
    "/metrics",
    "/openapi/v2",
    "/readyz",
    "/readyz/autoregister-completion",
    "/readyz/etcd",
    "/readyz/log",
    "/readyz/ping",
    "/readyz/poststarthook/apiservice-openapi-controller",
    "/readyz/poststarthook/apiservice-registration-controller",
    "/readyz/poststarthook/apiservice-status-available-controller",
    "/readyz/poststarthook/bootstrap-controller",
    "/readyz/poststarthook/crd-informer-synced",
    "/readyz/poststarthook/generic-apiserver-start-informers",
    "/readyz/poststarthook/kube-apiserver-autoregistration",
    "/readyz/poststarthook/rbac/bootstrap-roles",
    "/readyz/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/readyz/poststarthook/start-apiextensions-controllers",
    "/readyz/poststarthook/start-apiextensions-informers",
    "/readyz/poststarthook/start-cluster-authentication-info-controller",
    "/readyz/poststarthook/start-kube-aggregator-informers",
    "/readyz/poststarthook/start-kube-apiserver-admission-initializer",
    "/readyz/shutdown",
    "/version"
  ]
}

배치 api 그룹의 REST 엔드포인트 살펴보기

# apis/batch 엔드포인트 조회
curl http://localhost:8001/apis/batch

{
  "kind": "APIGroup",
  "apiVersion": "v1",
  "name": "batch",
  "versions": [                                    # 제공되는 groupVersion 종류(batch API그룹은 2가지 버전을 갖는다는것을 의미함)
    {
      "groupVersion": "batch/v1",
      "version": "v1"
    },
    {
      "groupVersion": "batch/v1beta1",
      "version": "v1beta1"
    }
  ],
  "preferredVersion": {                  # 클라이언트는 preferredVersion을 사용하는것을 권장한다는 의미
    "groupVersion": "batch/v1",
    "version": "v1"
  }
}
# batch/v1 리소스 유형
curl http://localhost:8001/apis/batch/v1

{
  "kind": "APIResourceList",         # batch/v1 API 그룹 내의 API 리소스 목록
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [                             # 이 그룹의 모든 리소스 유형을 담는 배열
    {
      "name": "jobs",
      "singularName": "",
      "namespaced": true,            # 네임스페이스에 속하는 리소스라는 의미 ( persistentvolumes같은 것들은 false)
      "kind": "Job",
      "verbs": [                               # 이 리소스와 함꼐 사용할 수 있는 제공되는 API(단일, 여러개를 한꺼번에 추가 삭제할수 있고, 검색, 감시 업데이트 할수 있음)
        "create",
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "update",
        "watch"
      ],
      "categories": [
        "all"
      ],
      "storageVersionHash": "mudhfqk/qZY="
    },
    {
      "name": "jobs/status",                  # 리소스의 상태를 수정하기 위한 특수한 REST 엔드포인트
      "singularName": "",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "get",
        "patch",
        "update"
      ]
    }
  ]
}

클러스터 안에 있는 모든 잡 인스턴스 나열하기

# job 생성
kubectl create -f my-job.yaml

# 모든 잡 인스턴스 조회
curl http://localhost:8001/apis/batch/v1/jobs

이름별로 특정 잡 인스턴스 검색

# api를 통한 job 조회
curl http://localhost:8001/apis/batch/v1/namespaces/default/jobs/my-job

# job 정보 조회
kubectl get job my-job -o json

# 위 2가지 결과는 같다.

8.2.2 파드 내에서 APi 서버와 통신

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

# curl을 위한 파드 생성
kubectl create -f curl.yaml

# 파드의 shell 접근
kubectl exec -it curl bash

API 서버 주소 찾기

# kubernetes 서비스
kubectl get svc

# KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT 변수를 통해 얻을수 있다.
env | grep KUBERNETES_SERVICE

KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443

# FQDN을 이용한 방법(403)
curl https://kubernetes -k 

서버의 아이덴티티 검증

# 컨테이너 내부에서 조회
ls /var/run/secrets/kubernetes.io/serviceaccount/

ca.crt  namespace  token

# --cacert 옵션을 통해 인증서 지정 ( 여전히 403)
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes

# 환경변수 지정
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# 재호출
curl https://kubernetes

API 서버로 인증

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# 호출  ( 왜안되느지 모르겠지만 안됨 ㅠ)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes

역할 기반 엑세스 제어(RBAC) 비활성화

# 모든 서비스 어카운트에 클러스터 관리자 권한이 부여됨
kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --group=system:serviceaccounts 

# 위험하고 프로덕션 클러스터에서는 해서는 안됨.

파드가 실행중인 네임스페이스 얻기

# namespace 지정 ( 실제로 default인데 아무값도 없다..)
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) 

# 호출
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v/namespaces/$NS/pods

파드가 쿠버네티스와 통신하는 방법 정리

스크린샷 2020-08-23 오후 11 02 49

82.3 앰배서더 컨테이너를 이용한 API 서버 통신 간소화

앰배서더 컨테이너 패턴 소개

스크린샷 2020-08-23 오후 11 03 46

추가적인 앰배서더 컨테이너를 사용한 curl 파드 실행

apiVersion: v1
kind: Pod
metadata:
  name: curl-with-ambassador
spec:
  containers:
  - name: main
    image: tutum/curl
    command: ["sleep", "9999999"]
  - name: ambassador                             # kubectl-proxy 이미지를 실행하는 앰배서더 컨테이너
    image: luksa/kubectl-proxy:1.6.2
# pod 생성
kubectl create -f curl-with-ambassador.yaml

# shell 접속
kubectl exec -it curl-with-ambassador -c main bash

# 호출
curl localhost:8001
스크린샷 2020-08-23 오후 11 09 33

8.2.4 클라이언트 라이브러리를 사용해 API 서버와 통신

Java 예제 ( https://github.com/kubernetes-client/java/ )

// list all pods
public class Example {
    public static void main(String[] args) throws IOException, ApiException{
        ApiClient client = Config.defaultClient();
        Configuration.setDefaultApiClient(client);

        CoreV1Api api = new CoreV1Api();
       // 라이브러리 메소드 설계는 좀 이상하게 해놓은듯, 모두 null이면 arguments 정의를 안했어야지..)
        V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
        for (V1Pod item : list.getItems()) {
            System.out.println(item.getMetadata().getName());
        }
    }
}

// watch on namespace object:
public class WatchExample {
    public static void main(String[] args) throws IOException, ApiException{
        ApiClient client = Config.defaultClient();
        Configuration.setDefaultApiClient(client);

        CoreV1Api api = new CoreV1Api();

        Watch<V1Namespace> watch = Watch.createWatch(
                client,
                api.listNamespaceCall(null, null, null, null, null, 5, null, null, Boolean.TRUE, null, null),
                new TypeToken<Watch.Response<V1Namespace>>(){}.getType());

        for (Watch.Response<V1Namespace> item : watch) {
            System.out.printf("%s : %s%n", item.type, item.object.getMetadata().getName());
        }
    }
}

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

스웨거 UI로 API 살펴보기

# minikube swaggerUI = true 적용
minikube start --extra-config=apiserver.Features.EnableSwaggerUI=true

# kubectl proxy
kubectl proxy --port=8080 & 

# 호출 ( swagger-ui 안됨..)
http://localhost:8080/swagger-ui
http://localhost:8080/swagger.json
http://192.168.64.2:8443/swagger-ui

8.3 요약