Open kmg28801 opened 1 year ago
$ kubectl cluster-info
Kubernetes control plane is running at https://34.68.118.85
GLBCDefaultBackend is running at https://34.68.118.85/api/v1/namespaces/kube-system/services/default-http-backend:http/proxy
KubeDNS is running at https://34.68.118.85/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
KubeDNSUpstream is running at https://34.68.118.85/api/v1/namespaces/kube-system/services/kube-dns-upstream:dns/proxy
Metrics-server is running at https://34.68.118.85/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy
$ curl https://34.68.118.85 -k
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
하지만 프록시 명령으로 인증서없이 통신할 수 있다.
kubectl proxy 명령은 프록시 서버를 실행해 로컬 컴퓨터에서 HTTP연결을 수신하고, 이 연결 인증을 관리하면서 API 서버로 전달하기 때문에, 요청할 때마다 인증 토큰을 전달할 필요가 없다.
$ kubectl proxy Starting to serve on 127.0.0.1:8001
해당 주소로 연결하면 된다.
kubectl은 필요한 모든 것(API 서버 URL, 인증 토큰 등)을 이미 알고 있으므로 프록시 되고있는 8001번 포트를 통해 연결을 수신하기 시작한다. proxy > API 서버 > proxy 아래 경로들은 파드, 서비스 등과 같은 리소스를 생성할 때 리소스 정의에 지정한 API 그룹과 버전에 해당한다 .
$ curl http://localhost:8001 { "paths": [ "/.well-known/openid-configuration", "/api", # 리소스 타입들 "/api/v1", "/apis", "/apis/", "/apis/admissionregistration.k8s.io", "/apis/admissionregistration.k8s.io/v1", "/apis/apiextensions.k8s.io", "/apis/apiextensions.k8s.io/v1", "/apis/apiregistration.k8s.io", "/apis/apiregistration.k8s.io/v1", "/apis/apps", "/apis/apps/v1", ... "/apis/batch", # 배치 API 그룹 "/apis/batch/v1", ...
배치 api 그룹
$ curl http://localhost:8001/apis/batch
{
"kind": "APIGroup",
"apiVersion": "v1",
"name": "batch",
"versions": [ # 배치 API 그룹은 2가지 버전을 갖는다.
{
"groupVersion": "batch/v1",
"version": "v1"
},
{
"groupVersion": "batch/v1beta1",
"version": "v1beta1"
}
],
"preferredVersion": { # 클라이언트는 v1 버전을 사용해야 한다.
"groupVersion": "batch/v1",
"version": "v1"
}
아래와 같이 batch/v1 API 그룹에서 노출된 리소스 유형 및 REST 엔드포인트 목록을 반환한다.
$ curl http://localhost:8001/apis/batch/v1
{
"kind": "APIResourceList", # batch/v1 API 그룹내의 API 리소스 목록
"apiVersion": "v1",
"groupVersion": "batch/v1",
"resources": [
{
"name": "cronjobs", # 네임스페이스 지정된(true로 설정된) 잡 리소스에 관한 설명 (/apis/batch/v1/cronjobs)
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [ # 사용할 수 있는 동사(동작)
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
},
{
"name": "cronjobs/status", # cronjobs 의 상태만 변경 가능한 API 엔드포인트
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"get",
"patch",
"update"
]
},
....
}
]
$ curl http://localhost:8001/apis/batch/v1/jobs
{
"kind": "JobList",
"apiVersion": "batch/v1",
"metadata": {
"resourceVersion": "449081"
},
"items": []
처음엔 아무 잡도 없기 때문에 items 가 비어있다. 아래와 같이 my-job.yaml 으로 잡을 띄우고 다시 조회해보자.
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 # 교재에서 제공해주는 샘플 잡
$ kubectl apply -f my-job.yaml
Warning: Autopilot set default resource requests for Job default/my-job, as resource requests were not specified. See http://g.co/gke/autopilot-defaults
job.batch/my-job created
$ curl http://localhost:8001/apis/batch/v1/jobs
{
"kind": "JobList",
"apiVersion": "batch/v1",
"metadata": {
"resourceVersion": "451356"
},
"items": [
{
"metadata": {
"name": "my-job", # 방금 띄워준 my-job 이 Items에 조회된다.
"namespace": "default",
"uid": "0268927a-5050-480f-9423-826c1545f06e",
"resourceVersion": "451332",
"generation": 1,
"creationTimestamp": "2023-01-07T15:06:31Z",
"labels": {
"app": "batch-job"
},
....
]
}
엔드포인트에 이름을 설정하여 특정 잡 인스턴스를 검색할 수 있다.
namespaces/{namespaces}/jobs/{job-name}
$ curl http://localhost:8001/apis/batch/v1/namespaces/default/jobs/my-job { "kind": "Job", "apiVersion": "batch/v1", "metadata": { "name": "my-job", "namespace": "default", "uid": "0268927a-5050-480f-9423-826c1545f06e", "resourceVersion": "454271", "generation": 1, "creationTimestamp": "2023-01-07T15:06:31Z", "labels": { "app": "batch-job" }, ... }
아래 명령어로도 동일하게 JSON 형태로 정보를 얻을 수 있다.
$ kubectl get job my-job -o json
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"annotations": {
"autopilot.gke.io/resource-adjustment": "{\"input\":{\"containers\":[{\"name\":\"main\"}]},\"output\":{\"containers\":[{\"limits\":{\"cpu\":\"500m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"2Gi\"},\"requests\":{\"cpu\":\"500m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"2Gi\"},\"name\":\"main\"}]},\"modified\":true}",
"batch.kubernetes.io/job-tracking": "",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"batch/v1\",\"kind\":\"Job\",\"metadata\":{\"annotations\":{},\"name\":\"my-job\",\"namespace\":\"default\"},\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"app\":\"batch-job\"}},\"spec\":{\"containers\":[{\"image\":\"luksa/batch-job\",\"name\":\"main\"}],\"restartPolicy\":\"OnFailure\"}}}}\n"
},
"creationTimestamp": "2023-01-07T15:06:31Z",
"generation": 1,
"labels": {
"app": "batch-job"
},
"name": "my-job",
"namespace": "default",
"resourceVersion": "454271",
"uid": "0268927a-5050-480f-9423-826c1545f06e"
},
...
}
8.2.1 에서는 kubectl proxy를 사용해 로컬에서 API 서버와 통신하는 방법을 배웠다.
kubectl이 없는 파드내에서 통신하는방법을 알아보자. 🔢
API통신을 위한 파드실행
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- name: main
image: tutum/curl # 컨테이너에서 curl을 사용해야 하기 때문에 tutum/curl 이미지 사용
command: ["sleep", "9999999"] # 컨테이너가 계속 실행되도록 하려고 지연 시간이 길게 sleep 커맨드를 실행한다.
위와 같이 띄우면 Docker Image 를 pulling 해올때 아래와 같이 에러가 발생한다.
kubectl get pods
NAME READY STATUS RESTARTS AGE
curl 0/1 ImagePullBackOff 0 4m1s
도커 허브 이슈 #1672 를 보면 tutum/curl
이미지는 삭제되었고 대신에 curlimages/curl
이미지를 사용하라고 가이드 되어있다.
$ kubectl exec -it curl bash
root@curl:/#
위와 같이 bash 명령을 날리면 $PATH 경로를 찾을 수 없다고 나와서(아마도 환경변수 설정이 안되있는듯) /bin/sh 로 명령을 날려야 한다.
$ kubectl exec -it curl /bin/sh
/ $
API서버 주소찾기
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.8.128.1 <none> 443/TCP 11h
/ $ env | grep KUBERNETES_SERVICE
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.8.128.1
각 서비스마다 DNS 엔트리가 있기 때문에 환경변수 조회할 필요없이, 단순시 curl에서 https://kubernetes 을 조회하기만 하면 된다.
$ kubectl exec -it curl /bin/sh
/ $ curl https://kubernetes
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.
하지만 조회하려고 하면 위와 같이 서버 인증서 확인이 안되었기 때문에 조회가 실패한다.
- 서버의 아이덴티티 검증 (서버 인증서 확인하기)
7장에서 시크릿을 설명했던 내용중에, 각 컨테이너의 /var/run/secrets/kubernetes.io/service/account 에 마운트되는 자동생성된 `default-token-xyz` 라는 이름의 시크릿이 있다. 이걸로 시크릿의 내용을 살펴본다.
```shell
/ $ ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token
위 파일중 ca.crt 파일이 API 서버의 인증서를 서명하는 데 사용되는 인증 기관(CA)의 인증서이다.
2. API 서버와 통신확인
을 위해서 서버의 인증서가 CA로 서명됐는지 확인하면 된다.
/ $ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
아직 인증에 실패한다.
우선 아래 명령을 통해 --cacert 지정을 환경변수로 대신해준다.
/$ export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
아래와 같이 동일한 명령을 간단하게 요청할 수 있다. (아직 인증은 안됨)
/ $ curl https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
API서버로 인증 이제 인증을 제대로 해보자.... 먼저 아래와 같이 토큰을 환경변수에 로드하고, 이를 사용해 API 서버에 액세스한다.
/$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
환경변수 토큰을 활용해서 API 서버에 억세스 해본다.
/ $ curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:serviceaccount:default:default\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
여전히 실패한다....
RBAC가 활성화 된 쿠버네티스에서는 서비스어카운트 등록해야 한다.(기본적으로 활성화 되어있음) 아래 명령어로 모든 서비스 어카운트(모든 파드)에 클러스터 관리자 권한이 부여돼 원하는 대로 액세스 할 수 있다.
이 방법은 위험하지만 테스트 목적으로는 괜찮다. 12장에서 RBAC에 대해서 배우면서 정상적인 방법을 알려 줄 것이다.
$ kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --group=system:serviceaccounts clusterrolebinding.rbac.authorization.k8s.io/permissive-binding created
이제 아래와 같이 모든 API 서버를 정상 조회할 수 있다. 👍
/ $ curl -H "Authorization: Bearer $TOKEN" https://kubernetes { "paths": [ "/.well-known/openid-configuration", "/api", "/api/v1", "/apis", "/apis/", "/apis/admissionregistration.k8s.io", "/apis/admissionregistration.k8s.io/v1", "/apis/apiextensions.k8s.io", "/apis/apiextensions.k8s.io/v1", "/apis/apiregistration.k8s.io", "/apis/apiregistration.k8s.io/v1", "/apis/apps", "/apis/apps/v1", ... ] }
파드가 실행중인 네임스페이스 얻기
/ $ NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
/ $ curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "489452"
},
"items": [
{
"metadata": {
"name": "curl",
"namespace": "default",
...
},
...실행중인 모든 파드...
}
위와 같은 방식으로 다른 API 오브젝트를 검색하고 GET, PUT, PATCH 를 전송해 업데이트도 할 수 있다. 💯
파드각 쿠버네티스와 통신하는 방법 정리 🔢
인증서의 유효성검사를 조금더 간단히 해보자. API서버로 직접 요청을 보내는 대신 프록시로 요청을 보내 검증을 처리하게 하자.
이건 보안을 유지하면서 통신을 간단하게 만드는 방법이다.
kubectl proxy 와 동일한 기능을 수행하는 메인 컨테이너 옆에 앰배서더 컨테이너가 HTTPS 연결 처리 및 시크릿 설정을 대신하도록 한다.
이를 통해 메인 컨테이너는 HTTP 로 인증서 절차 없이 액세스 할 수 있다.
아래와 같이 앰배서더를 포함한 2개의 컨테이너가 존재하는 파드를 배포 하자
apiVersion: v1
kind: Pod
metadata:
name: curl-with-ambassador
spec:
containers:
- name: main
image: curlimages/curl # 여기도 curlimages/curl 로 바꿔준다
command: ["sleep", "9999999"]
- name: ambassador
image: luksa/kubectl-proxy:1.6.2 # kubectl-rpoxy 이미지를 실행하는 앰버서더 컨테이너 이미지
bash 명령도 /bin/sh 로 바꿔서 실행해본다.
$ kubectl exec -it curl-with-ambassador -c main /bin/sh
/ $
위처럼 컨테이너가 두개여서 -c옵션을 통해 main 컨테이너라는걸 명시해줘야 한다.
이제 localhost 로 그냥 조회할 수 있다. 👍
/ $ curl localhost:8001
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
"/apis/",
"/apis/admissionregistration.k8s.io",
"/apis/admissionregistration.k8s.io/v1",
"/apis/apiextensions.k8s.io",
"/apis/apiextensions.k8s.io/v1",
"/apis/apiregistration.k8s.io",
"/apis/apiregistration.k8s.io/v1",
"/apis/apps",
"/apis/apps/v1",
...
]
}
복잡성을 숨기고, 메인컨테이너를 단순화시킬 수 있고 여러 어플리케이션에서 재사용 할 수 있지만, 추가프로세스와 추가리소스를 소비한다는것이 단점이다.
import java.util.Arrays;
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;
public class Test { 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("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("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("programmatically-created-pod")
.delete();
System.out.println("Deleted the pod");
} }
- 그외에도 스웨거 API를 활성화 시켜서 사용하는 방법도 있다. (REST API를 탐색하는 더 좋은 방법)
8. 애플리케이션에서 파드 메타데이터와 그 외의 리소스에 액세스하기
8.1 Downward API로 메타데이터 전달
8.1.1 사용 가능한 메타데이터 이해
Downward API를 이용해 다음 정보를 컨테이너에 전달할 수 있다.
8.1.2 환경변수로 메타데이터 노출하기
아그래 그림과 같이 환경변수를 이용해 파드의 메타데이터와 속성을 파드에 노출한다.
파드를 만든 후에 아래와 같이 kubectl exec 를 사용해서 컨테이너의 모든 환경변수를 확인
8.1.3 downward API 볼륨에 파일로 메타데이터 전달
-환경변수가 아닌 파일로 메타데이터를 노출하기위해 downwardAPI 볼륨을 정의하고 컨테이너에 마운트한다.
환경변수 대신 downward 라는 볼륨을 정의하고 컨테이너의 /Etc/downward 아래에 마운트한 것이다.
파일을 조회하면 아래와 같이 확인할 수 있다.