반응형

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

이전 Downward API와 메타데이터 포스팅에서 DwonwardAPI가 어떻게 특정 파드와 컨테이너의 메타데이터를 그 안에서 실행되는 프로세스를 쉽게 전달하는지 살펴봤습니다. Downward API는 단지 파드 자체의 메타데이터와 모든 파드의 데이터 중 일부만 노출합니다. 그러나 때때로 애플리케이션에서 클러스터에 정 의된 다른 파드나 리소스에 관한 더 많은 정보가 필요할 수도 있습니다. 이 경우 Downward API는 도움이 되지 않습니다.

 

서비스와 파드에 관한 정보는 서비스 관련 환경변수나 DNS로 얻을 수 있습니다. 그러나 애플리케이션이 다른 리소스의 정보가 필요하거나 가능한 한 최신 정보에 접근해야 하는 경우 API 서버와 직접 통신해야 합니다.

 

파드 내의 애플리케이션이 쿠버네티스 API 서버와 통신하는 방법을 살펴보기 전에 먼저 로컬 컴퓨터에서 서버의 REST 엔드포인트를 살펴본 후 API 서버와 통신하는 방법을 확인해보겠습니다.

 

쿠버네티스 REST API 살펴보기

쿠버네티스 API와 통신 하는 애플리케이션을 개발하기 위해 API에 관해 먼저 알아봐야 합니다. API에 알아보기 위해서 API 서버에 직접 접속해 보는 방법이 있습니다. kubectl cluster-info를 실행해 URL을 얻을 수 있습니다.

 

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443

 

 서버는 HTTPS를 사용하고 인증이 필요하기 때문에 직접 통신하는 것은 간단하지 않습니다. curl을 사용해 접속하고 curl의 --insecure(또는 -k) 옵션을 사용해 서버 인증서 확인을 건너뛰도록 시도해볼 수 있지만 원하는 결과를 얻지는 못할 것 입니다.

 

$ curl https://192.168.99.100:8443-k
Unauthorized

 

하지만 다행히도 인증을 직접 처리하는 대신 kubectl proxy 명령을 실행해 프록시로 서버와 통신할 수 있습니다.

 

kubectl 프록시로 API 서버 액세스하기

kubectl proxy 명령은 프록시 서버를 실행해 로컬 컴퓨터에서 HTTP 연결을 수신하고 이 연결을 인증을 관리하면서 API 서버로 전달하기 때문에, 요청할 때마다 인증 토큰을 전달할 필요가 없습니다. 또한 각 요청마다 서버의 인증서를 확인해 중간자(man in the middle)가 아닌 실제 API 서버와 통신한다는 것을 담보합니다.

 

프록시를 실행하는 것은 생각보다 아주 쉽습니다. 다음 순서로 명령어를 입력해줍니다.

 

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

 

 kubectl은 필요한 모든 것(API 서버 URL, 인증 토큰 등)을 이미 알고 있으므로 다른 인자 를 전달할 필요가 없습니다. 시작하자마자 프록시는 로컬 포트 8001에서 연결을 수신하기 시작합니다. 잘 작동하는지 살펴봅시다.

 

$ curl localhost:8001
{
paths": [
"/api",
"/api/v1",
...

 

이렇게 요청을 프록시로 보내면 이 요청을 API 서버로 보낸 다음 프록시는 서버가 반환하는 모든 것을 반환하게 됩니다. 이제 내용을 살펴보겠습니다.

 

kubectl proxy로 쿠버네티스 API 살펴보기

curl을 계속 이용하거나 웹 브라우저를 열어 http://localhost:8001로 접속할 수 있게 되었습니다. 기본 URL에 접속해 API 서버가 무엇을 반환하는지 좀 더 면밀히 살펴보겠습니다. 서버는 다음 예제와 같이 경로 목록을 반환합니다.

 

  • 예제 1) API 서버의 REST 엔드포인트 목록: http://localhost:8001
$ curl http://localhost:8081
{
"paths": [
"/api",                     
"/api/v1",                            > 대부분의 리소스 타입을 여기서 확인할 수 있습니다.
"/apis",
"/apis/apps",
"/apis/apps/v1beta1",
...
"/apis/batch",                     > batch API 그룹과 이 그룹의 두 가지 버전
"/apis/batch/v1",
"/apis/batch/v2alpha1",

 

 이러한 경로는 파드, 서비스 등과 같은 리소스를 생성할 때 리소스 정의에 지정한 API 그룹과 버전에 해당합니다. 이전 쿠버네티스 포스팅들을 정독했다면 /apis/batch/v1 경로의 batch/v1가 잡 리소스의 API 그룹과 버전이란 것을 알아차릴 수 있을 것입니다.

 

마찬가지로 /api/v1은 생성한 일반적인 리소스(Pods, Services, ReplicationController 등)에서 참조하는 apiVersion: v1에 해당합니다. 초기의 쿠버 네티스는 API 그룹 개념을 사용하지 않았기 때문에, 초기 버전의 쿠버네티스에 도입된 가장 일반적인 리소스 유형들은 특정 그룹에 속하지 않습니다. API 그룹은 나중에 도입됐습니다. 때문에 API 그룹이 없는 초기 리소스 유형은 이제 core API 그룹에 속하는 것으로 간주됩니다.

 

  • 쿠버네티스에 관한 다른 글들은 다음 페이지에 정리해되어있습니다.
 

'Container/Kubernetes' 카테고리의 글 목록

IT에 입문하며 배우는 것들과 뉴스들을 요약해 올리고 있습니다. 모든 방문자들에게 감사를 표하며 행운이 가득하길.

easyitwanner.tistory.com

 

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

잡 리소스 API를 살펴보겠습니다. 다음 예제에 표시된 대로 /apis/batch 경로 뒤에 무엇이 있는지 살펴보겠습니다. 이 예제에서는 버전이 생략되었습니다.

 

  • 예제 2) /apis/batch 아래 있는 엔드포인트 조회: http://localhost:8001/apis/batch
$ curl http://localhost:8001/apis/batch
{
  "kind": "APIGroup",
  "apiVersion": "v1",
  "name": "batch",
  "versions": [
    {
      "groupVersion": "batch/v1",                 >  두 가지 버전을 갖는 batch API 그룹
      "version": "v1"
    },
    {
      "groupVersion": "batch/v2alpha1",       > 두 가지 버전을 갖는 batch API 그룹
      "version": "v2alpha1"
    }
  ],
  "preferredVersion": {
    "groupVersion": "batch/v1",                     > 클라이언트는 v2alpga1 대신 v1 버전을 사용해야 합니다.
    "version": "v1"
  },
  "serverAddressByClientCIDRs": null
}

 

 응답에는 사용 가능한 버전과 클라이언트가 사용해야 하는 선호 버전에 관한 정보와 batch API 그룹에 관한 설명이 표시됩니다. 계속해서 /apis/batch/v1 경로 뒤에 무엇이 있는지 살펴보겠습니다.

 

  • 예제 3) batch/v1 내의 리소스 유형: http://localhost:8001/apis/batch/v1
$ curl http://localhost:8001/apis/batch/v1
{
  "kind": "APIResourceList",     >  batch/v1 API 그룹 내의 API 리소스 목록
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [                         <  이 그룹의 모든 리소스 유형을 담는 배열
    {
      "name": "jobs",
      "namespaced": true,          >  네임스페이스 지정(namespaced) 필드가 true인 잡 리소스에 관한 설명
      "kind": "Job",
      "verbs": [
        "create",
        "delete",
        "deletecollection",            >  이 리소스와 함께 사용할 수 있는 동사들은 다음과 같습니다(잡을 생성할 수 있고,
        "get",                                    각각 또는 여러 개를 한꺼번에 삭제할 수 있으며 검색, 감시, 업데이트할 수 
        "list",                                    있습니다).
        "patch",
        "update",
        "watch"
      ]
    },
    {
      "name": "jobs/status",         <  리소스는 상태를 수정하기 위한 특수한 REST 엔드포인트가 있습니다.
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "get",                                 >   상태 정보는 검색, 패치, 업데이트할 수 있습니다.
        "patch",
        "update"
      ]
    }
  ]
}

 

보다시피 API 서버는 batch/v1 API 그룹에서 리소스 유형 및 REST 엔드포인트 목록을 반환합니다. 그중 하나가 잡 리소스입니다. API 서버는 리소스 이름과 관련 kind 외에도 리소 스에 네임스페이스가 지정됐는지(namespaced) 여부, 짧은 이름(있는 경우에 표시합니다. 잡은 짧은 이름이 없습니다)과 해당 리소스에 사용할 수 있는 동사(verbs) 목록도 갖고 있습니다.

 

반환된 목록은 API 서버에 노출된 REST 리소스를 설명합니다. "name": "jobs"는 API 에/apis/batch/v1/jobs 엔드포인트가 포함돼 있음을 알려줍니다. "verbs" 배열은 해당 엔 드포인트로 잡 리소스를 검색, 업데이트, 삭제할 수 있음을 알려줍니다. 특정 리소스의 경우 추가 API 엔드포인트도 노출됩니다(예: iohs/status 경로는 잡의 상태만 변경 가능합니다).

 

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

클러스터에서 잡목록을 얻으려면 다음 예제와 같이 /apis/batch/v1/jobs 경로에서 GET 요청을 실행해야 합니다.

  • 예제 4) 잡 목록: http://localhost:8001/api/s/batch/v1/jobs
$ curl http://localhost:8001/apis/batch/v1/jobs
{
    "kind": "JobList",
    "apiVersion": "batch/v1",
    "metadata": {
        "selfLink": "/apis/batch/v1/jobs",
        "resourceVersion": "225162"
},
"items": [
{
    "metadata": {
        "name": "my-job",
        "namespace": "default",

 

 클러스터에 잡 리소스가 배포돼 있지 않기 때문에 항목(items) 배열이 비어 있을 것입니다. Chapter08/my-job.yaml에 있는 잡을 배포하고 REST 엔드포인트를 다시 접속하면 위 예제 4와 동일한 결과를 얻을 수 있습니다.

 

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

앞 예제의 엔드포인트는 모든 네임스페이스의 모든 잡 목록을 반환했습니다. 하나의 특정 잡을 반환하려면 URL에 이름과 네임스페이스를 지정해야 합니다. 앞 예제에 표시된 잡(name: my- job; namespace: default)을 검색하려면 다음 예제와 같이 /apis/batch/v1/namespaces/default/jobs/my-job 경로를 요청해야 합니다.

 

  • 예제 5) 이름으로 특정 네임스페이스에 있는 리소스 검색
$ curl http://localhost:8001/apis/batch/v1/namespaces/default/jobs/my-job
{
    "kind": "Job",
    "apiVersion": "batch/v1",
    "metadata": {
        "name":"my-job",
        "namespace": "default",

 

 보다시피 다음 명령을 실행한 것과 정확히 동일하게 my-job 잡 리소스에 관한 전체 JSON 정의를 얻을 수 있습니다.

 

$ kubectl get job my-job -o json

 

특별한 도구를 사용하지 않고도 쿠버네티스 REST API 서버를 탐색할 수 있지만, REST API 전체를 탐색하고 상호작용하기 위한 더 나은 옵션은 나중에 다뤄보도록 하겠습니다. 지금은 이렇게 curl을 사용해 둘러보는 것만으로도 파드에서 실행되는 애플리케이 션이 쿠버네티스와 어떻게 통신하는지 이해하기에 충분할 것입니다.

 

파드 내에서 API 서버와 통신

kubectl proxy를 사용해 로컬 컴퓨터에서 API 서버와 통신하는 방법을 배웠습니다. 이제 자주 사용되는 kubectl이 없는 파드 내에서 통신하는 방법을 알아보겠습니다. 파드 내부에서 API 서버와 통신하려면 다음 세 가지를 처리해야 합니다.

 

  •  API 서버의 위치를 찾아야 합니다.
  •  API 서버인 척 가장하는 누군가와 통신하는 것이 아니라) API 서버와 통신하고 있는지 확인해야 합니다.
  •  API 서버로 인증해야 한다. 그렇지 않으면 볼 수도 없고 아무것도 할 수 없습니다.

 다음 부분에서 이 작업을 수행하는 방법을 살펴보겠습니다.

 

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

 

가장 먼저 필요한 것은 API 서버와 통신할 파드입니다. (sleep 명령을 실행하는 컨테이너 하나만 있는) 아무것도 하지 않는 파드를 실행한 다음, 컨테이너의 셸에서 kubectl exec를 실행한다. 그런 다음 curl을 사용해 해당 셸 내에서 API 서버에 액세스할 것입니다.

 

curl 바이너리가 포함된 컨테이너 이미지를 사용해야 합니다. 도커 허브에서 tutum/ curl 이미지를 검색해서 사용할 수 있습니다(curl 바이너리를 포함하는 다른 기존 이미지를 사용하거나 직접 만들 수도 있습니다). 파드 정의는 다음 예제에 표시돼 있습니다.

 

  • 예제 6) API 서버와 통신을 시도하는 파드: curl.yaml
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
    - name: main
      image: tutum/curl                        >  컨테이너에서 curl을 사용해야 하기 때문에 tutum/curl 이미지를 사용합니다.
      command: ["sleep", "9999999"]  >  컨테이너가 계속 실행되도록 하려고 지연 시간이 길게
                                                              sleep 커맨드 실행합니다.

 

파드를 만든 후 kubectl exec를 실행해 컨테이너 내부에서 bash 셸을 실행합니다.

 

$ kubectl exec -it curl bash
root@curl:/#

 

이제 API 서버와 통신할 준비가 됐습니다.

 

API 서버 주소 찾기

 

먼저 쿠버네티스 API 서버의 IP와 포트를 찾아야 합니다. kubernetes라는 서비스가 디폴트 네임스페이스에 자동으로 노출되고 API 서버를 가리키도록 구성되기 때문에 쉽습니다. kubectl get svc를 사용해 서비스를 조회할 수 있습니다.

 

$ kubectl get svc

 

그리고 각 서비스에 관해 환경변수가 구성돼 있습니다. API 서버의 IP 주소와 포트를 컨테이너 내부의 KUBERNETES_SERVICE_HOST와 KUBERNETES_SERVICE _PORT 변수에서 모두 얻을 수 있습니다.

 

root@curl:/#env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT-443
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT_HTTPS-443

 

또한 각 서비스마다 DNS 엔트리가 있으므로 환경변수를 조회할 필요도 없이, 단순히 curl에서 https://kubernetes를 가리키기만 하면 됩니다. 서비스가 어느 포트에서 제공되는지 모를 경우 서비스의 실제 포트 번호를 얻기 위 해 환경변수를 조회하거나 DNS SRV 레코드 조회를 수행해 확인할 수 있습니다.

 

위 예제에 표시된 환경변수에 따르면 API 서버가 HTTPS의 기본 포트인 443에서 수 신 대기 중이므로 HTTPS로 서버에 접속할 수 있습니다.

 

root@curl:/# curl https://kubernetes
curl: (60) SSL certificate problem: unable to get local issuer certificate
...
If you'd like to turn off curl's verification of the certificate, use the-k (or --insecure) option.

 

 이 문제를 해결하는 가장 간단한 방법은 제안된 -K 옵션을 사용하는 것(그리고 이것이 API 서버를 수동으로 사용할 때 일반적으로 사용하는 방법)이지만, 좀 더 길어도 올바른 방법을 살펴보겠습니다. 연결하려는 서버가 인증된 API 서버라는 것을 맹목적으로 신뢰하는 대신 인증서를 curl로 검사해 인증서를 확인합니다.

 

실제 애플리케이션에서는 서버 인증서 확인을 절대로 건너뛰면 안 됩니다. 그렇게 하면 중간자 공격(man-in-the-middle attack)으로 애플리케이션의 인증 토큰을 공격자에게 노출시킬 수 있습니다.

 

  • '중간자 공격'은 통신을 연결하는 두 사람 사이에 중간자가 침입해 두 사람은 상대방에게 연결했다고 생각하지만 실제로는 두 사 람은 중간자에게 연결돼 있으며 중간자가 한쪽에서 전달된 정보를 도청 및 조작한 후 다른 쪽으로 전달하는 것을 말합니다.

서버의 아이덴티티 검증

 

이전에 시크릿을 설명하면서 각 컨테이너의 /var/run/secrets/kubernetes.io/service account/에 마운트되는 자동으로 생성된 "default-token-cfee9"라는 이름의 시크릿을 살펴 봤습니다. 해당 디렉터리의 파일을 조회해 해당 시크릿의 내용을 다시 살펴보겠습니다. 시크릿에 대한 설명은 다음 페이지에서 다룬적 있으니 참고 바랍니다.

 

[Kubernetes Secrets] 쿠버네티스 시크릿(Secret)이란?

목차 시크릿 기본 토큰 시크릿 시크릿 생성 컨피그맵과 시크릿 비교 stringData 필드 파드에서 시크릿 항목 읽기 파드에서 시크릿 사용 HTTPS를 활성화하도록 fortune-config 컨피그맵 수정 fortune-https 시

easyitwanner.tistory.com

 

root@curl:/#1s/var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token

 

 시크릿에는 세 개의 항목이 있습니다(그래서 시크릿 볼륨에 세 개의 파일이 있다). 지금은 쿠버네 티스 API 서버의 인증서에 서명하는 데 사용되는 인증 기관(CA)의 인증서를 보유한 ca.crt 파일에 집중하려 합니다. API 서버와 통신 중인지 확인하려면 서버의 인증서가 CA로 서명 됐는지 확인해야 합니다. curl을 --cacert 옵션과 같이 사용하면 CA 인증서를 지정할 수 있으므로 API 서버를 다시 접속합니다.

 

root@curl:/# curl --cacert/var/run/secrets/kubernetes.io/serviceaccount/ca/crt https://kubernetes
Unauthorized

 

  • 이 때 "Unauthorized"보다 더 긴 오류 설명이 표시될 수도 있습니다.

 

서버의 인증서를 신뢰할 수 있는 CA가 서명했기 때문에 curl이 서버의 ID를 확인했습니다. Unauthorized에서 알 수 있듯이 여전히 인증 처리가 필요합니다. 잠시 뒤 시도해보겠지만 먼저 CURL_CA_BUNDLE 환경변수를 설정해 편하게 할 수 있는 방법을 살펴보겠습니다. 이렇게 하면 curl을 실행할 때마다 --cacert를 지정할 필요가 없게 됩니다..

 

root@curl:/# export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.

 

--cacert를 사용하지 않고 API 서버에 접속할 수 있다.

 

root@curl:/# curl https://kubernetes
Unauthorized

 

이제 독자의 클라이언트(curl)는 API 서버를 신뢰하지만 API 서버 자체는 여러분이 누구인지 모르기 때문에 액세스 권한이 없다고 표시할 것입니다.

 

API 서버로 인증

 

서버에서 인증을 통과해야 클러스터에 배포된 API 오브젝트를 읽고, 업데이트와 삭제를 할 수 있습니다. 인증하려면 인증 토큰이 필요합니다. 다행히 토큰은 전에 언급한 default- token 시크릿으로 제공되며 시크릿 볼륨의 token 파일에 저장됩니다. 시크릿 이름에서 알 수 있듯이, 시크릿의 주된 목적이 바로 이것입니다.

 

토큰을 사용해 API 서버에 액세스합니다. 먼저 토큰을 환경변수에 로드해야 합니다.

 

root@curl:/#TOKEN=$(cat/var/run/secrets/kubernetes.io/serviceaccount/token)

 

토큰은 이제 TOKEN 환경변수에 저장됩니다. 다음 예제와 같이 API 서버로 요청을 보낼 때 사용할 수 있습니다.

 

  • 예제 7) API 서버로부터 적절한 응답 받기
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes on
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1beta1",
    "/apis/authorization.k8s.io",
    // ...
    "/ui/",
    "/version"
  ]
}

 

보다시피 요청의 Authorization HTTP 헤더 내부에 토큰을 전달했습니다. API 서버는 토큰을 인증된 것으로 인식하고 적절한 응답을 반환했습니다. 이제 앞의 절들에서 수행한 방식으 로 클러스터의 모든 리소스를 탐색할 수 있습니다.

 

예를 들어 동일한 네임스페이스 내에 있는 모든 파드를 조회할 수 있습니다. 그러나 먼저 curl 파드가 어떤 네임스페이스에서 실행 중인지 알아야 합니다.

 

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

 

RBAC가 활성화된 쿠버네티스 클러스터를 사용하는 경우 서비스 어카운트가 API 서버에 액세스할 권한이 없을 수 있습니다. 서비스 어카운트와 RBAC에 대해서는 나중에 따로 다루겠지만 이 포스팅에서는 API 서버를 쿼리할 수 있는 가장 간단한 방법인 다음 명령을 실행해 RBAC를 우회하는 것에 대해 설명하겠습니다.

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

 

 이렇게 하면 모든 서비스 어카운트(모든 파드라고도 말할 수 있다)에 클러스터 관리자 권한이 부여돼 원하는 대로 할 수 있습니다. 이렇게 하는 것은 분명히 위험하고 프로덕션 클러스터에서는 절대 해서는 안 됩니다. 단, 테스트 목적이라면 상관없습니다.

 

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

 

바로 이전 포스팅인 Downward API 부분에서 해당 API로 네임스페이스를 파드에 전달하는 방법을 살펴봤습니다. 여기서 주의 깊게 봤다면 시크릿 볼륨에 네임스페이스라는 파일이 포함돼 있음을 보셨을 수도 있을 것 입니다. 이 파일에는 파드가 실행 중인 네임스페이스가 포함돼 있으므로, 환경변 수로 파드에 네임스페이스를 명시적으로 전달하는 대신 파일을 읽을 수 있습니다. 파일 내용을 NS 환경변수에 로드한 뒤 다음 예제와 같이 모든 파드를 나열해보겠습니다.

 

  • 예제 8) 파드가 속한 네임스페스에 있는 파드 나열하기
root@curl:/#NS=$(cat/var/run/secrets/kubernetes.io/serviceaccount/namespace)
root@curl:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
{
"kind": "PodList",
"apiVersion": "v1",
...

 

 마운트된 시크릿 볼륨 디렉터리에 있는 3개의 파일을 사용해 파드와 동일한 네임스페 이스에서 실행 중인 모든 파드를 나열했습니다. 같은 방식으로 다른 API 오브젝트를 검색하고 간단한 GET 요청 대신 PUT 또는 PATCH를 전송해 업데이트할 수도 있습니다.

 

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

파드 내에서 실행 중인 애플리케이션이 쿠버네티스 API에 적절히 액세스할 수 있는 방법을 간단히 정리하면 다음과 같습니다.

 

  • 애플리케이션은 API 서버의 인증서가 인증 기관으로부터 서명됐는지를 검증해야 하며, 인증 기관의 인증서는 ca.cart 파일에 있습니다.
  • 애플리케이션은 token 파일의 내용을 Authorization HTTP 헤더에 Bearer 토큰으로 넣어 전송해서 자신을 인증해야 합니다.
  • namespace 파일은 파드의 네임스페이스 안에 있는 API 오브젝트의 CRUD 작업을 수행할 때 네임스페이스를 API 서버로 전달하는 데 사용해야 합니다.
    • CRUD는 Create, Pead, Update, Delete를 나타냅니다. 해당 HTTP 메서드는 각각 POST. GET, PATCH/PUT, DELETE입니다.

 

 

 

반응형