CKA를 준비해보자 5일차 - Imperative vs Declarative commands

0

CKA

목록 보기
5/43

Imperative vs Declarative

kubernetes object를 생성하고 설정하는 방식에는 두 가지 방식이 있는데, 하나는 imperative(명령적) 방식과 하나는 Declarative(선언적) 방식이다.

imperative는 좌회전, 우회전, 직진과 같이 매번의 course마다 관리자가 cluster를 직접 관리하는 방식이다. 즉, 하나부터 열까지 모두 알려주고, 관리자 역시도 현재와 과거의 상태를 기억하고 있어야 한다.

반면 declarative는 어디로 가라는 명령만주고 알아서 가는 것과 같다. 어떻게 할지, 무엇을 할지에 대해서는 시스템이 자동으로 처리하고 목적지만 이야기하여 원하는 결과만 얻으면 되는 것이다. 이는 관리자가 현재와 과거의 히스토리를 알 필요없이 시스템이 모든 것들을 다 처리해준다는 장점이 있다.

kubernetes에서 imperative방식은 명령어 하나하나를 실행하여 모든 것들을 직접 설정하고 제어하는 것을 말한다.

kubectl run --image=nginx nginx
kubectl create deployment --image=nginx nginx
kubectl expose deployment nginx --port 80
kubectl edit deployment nginx
kubectl scale deployment nginx --replicas=5
kubectl set image deployment nginx nginx=nginx1.18
kubectl create -f nginx.yaml
kubectl replace -f nginx.yaml
kubectl delete -f nginx.yaml

반면 Declarative방식으로는 cluster에서 app과 service를 정의한 파일을 kubectl을 통해서 실행하여, 인프라에서 필요한 것이 무엇인지 결정할 수 있다. 즉 declarative 방식은 사용자가 너무 깊이 현재의 cluster의 상태를 알지 않고도 시스템에서 적절하게 사용자가 원하는 것을 실행시켜준다는 것이다. kubectl apply를 사용하여 object를 생성, 수정, 삭제할 수 있다.

kubectl apply -f nginx.yaml

파일을 보고 기존 구성에서 어떤 변화가 필요한 지 알아내는 것이다.

Imperative Commands

Imperative 명령어는 두 가지 종류가 있다.

  1. Create objects
kubectl run --image=nginx nginx
kubectl create deployment --image=nginx nginx
kubectl expose deployment nginx --port 80

새로운 object를 만들 때 사용한다. 위의 명령어처럼도 사용할 수 있지만, -f옵션을 통해 파일로도 설정이 가능하다.

kubectl create -f nginx.yaml

만약 pod가 이미 있는데, create명령어를 사용하면 AlreadyExists 에러가 발생한다.

  1. Updata Objects
kubectl edit deployment nginx
kubectl scale deployment nginx --replicas=5
kubectl set image deployment nginx nginx=nginx:1.18

update할 때도 파일을 통해서 적용할 수 있는데, 이 때 사용하는 것이 kubectl replace이다.

kubectl replace -f nginx.yaml
kubectl replace --force -f nginx.yaml

local file을 수정하여 반영하는 방식이 edit으로 실행하는 것보다 좋은 이유는, 로컬 파일에 히스토리가 남기 때문이다. edit으로 직접 실행해버리면 무엇이 수정되고 반영되었는 지, 알 방법이 없다.

만약 replace command를 실행하는데, 해당하는 pod가 없다면 Conflict error가 발생한다. 즉, replace할 pod가 없으니 충돌이 발생했다는 것이다.

이러한 Imperative 방식은 이전 환경을 기억하고 히스토리를 기록해야하기 때문에 사용하기 매우 어렵다.

Declarative Commands

Declarative 명령어 역시도 두 가지 방식을 가지고 있는데, 둘 다 동일한 명령어인 kubectl apply를 사용한다.

  1. Create objects
kubectl apply -f nginx.yaml
kubectl apply -f /path/to/config

재밌는 것은 특정 파일 뿐만 아니라, 디렉터리도 지정할 수 있다는 것이다. 디렉토리에 있는 모든 yaml파일들을 실행하게 되는 것이다.

  1. Update objects
kubectl apply -f nginx.yaml

Declarative 명령어 방식이 좋은 점은 Imperative방식과는 달리 이전 환경을 고려하지 않아도 된다는 것이다. 가령, pod를 생성할 때 kubectl create명령어는 이전에 이미 동일한 pod가 있다면 에러를 발생시키지만, kubectl apply는 발생시키지 않고 변화된 부분만 찾아서 적용한다.

반대로 kubectl replace는 수정하려는 pod가 없으면 에러를 발생시키만, kubectl apply는 에러를 발생시키지 않고 새로운 pod를 만들어낸다.

따라서, 관리할 때는 kubectl apply를 사용하는 것이 좋다.

kubectl apply

어떻게 kubectl apply가 내부적으로 동작하는 지 알아보도록 하자.

다음과 같은 kubernetes 설정 파일이 local에 있다고 하자.

  • nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    type: front-end-service
spec:
  containers:
    - name: nginx-container
      image: nginx:1.18

먼저, cluster에 해당 pod가 없는 경우이다.

kubectl apply -f ./nginx.yaml

이 다음은 kubernetes의 live object configurationnginx.yaml이 메모리로 저장되는데, 상태 관리를 위한 status가 추가된다.

  • kubernetes안의 live object configuration
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    type: front-end-service
spec:
  containers:
    - name: nginx-container
      image: nginx:1.18
status:
  conditions:
  - lastProbeTime: null
    status: "True"
    type: Initialized

이 다음, kubernetes는 바로 이 yaml 파일만으로 configuration을 설정하지 않는다. 이 yaml파일을 json파일로 변환하여 cluster에 적용해 pod를 만들어내는 것이다. 즉, 위의 yaml은 정보 저장용이고 실제로 적용되어 pod가 만들어지는 것은 다음의 json파일이다.

  • last applied configuration
{
    "apiVerson": "v1",
    "kind": "Pod",
    "metadata": {
        "annotations": {},
        "labels": {
            "run": "myapp-pod",
            "type": "front-end-service"
        },
        "name": "myapp-pod",
    },
    "spec": {
        "containers": [
            {
                "image": "nginx:1.18",
                "name": "nginx-container"
            }
        ]
    }
}

그런데 만약, 사용자가 local file을 수정했다고 하자. image를 nginx:1.18에서 nginx:1.19로 바꾸는 것이다.

  • nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    type: front-end-service
spec:
  containers:
    - name: nginx-container
      image: nginx:1.19

업데이트를 적용하자.

kubectl apply -f ./nginx.yaml

이 다음 kubernetes cluster 메모리에 있던 데이터가 바꾸게된다.

  • kubernetes안의 live object configuration
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    type: front-end-service
spec:
  containers:
    - name: nginx-container
      image: nginx:1.19
status:
  conditions:
  - lastProbeTime: null
    status: "True"
    type: Initialized

마지막으로 해당 yaml이 json으로 변환되어 cluster에 적용된다.

  • last applied configuration
{
    "apiVerson": "v1",
    "kind": "Pod",
    "metadata": {
        "annotations": {},
        "labels": {
            "run": "myapp-pod",
            "type": "front-end-service"
        },
        "name": "myapp-pod"
    },
    "spec": {
        "containers": [
            {
                "image": "nginx:1.19",
                "name": "nginx-container"
            }
        ]
    }
}

그런데 왜 json으로된 last applied configuration이 필요할까??

만약 local file인 nginx.yaml에서 라벨을 하나 지운다고 하자.

  • nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    # type: front-end-service
spec:
  containers:
    - name: nginx-container
      image: nginx:1.19

그리고 kubectl apply를 적용했을 떄, kubernetes에서는 어떻게 어떤 부분들이 추가되고, 변경되고, 삭제되었는 지 알 수 있을까??

이를 알기위해서 kubernetes는 json으로된 last applied configuration을 사용해, 입력으로 들어온 local file과 현재 적용된 json파일을 비교하는 것이다.

그러면 무엇이 추가되었고, 무엇이 삭제되었으며, 무엇이 변경되었는 지를 비교할 수 있기 때문이다. 이를 토대로 kubernetes에서는 메모리에 있는 live object configuration을 수정하는 것이다.

위의 경우 local filelast applied configuration을 먼저 비교하여 type: front-end-service가 삭제되었다는 것을 알고, live object configuration에 적용하는 것이다.

  • live object configuration
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
    - name: nginx-container
      image: nginx:1.19
status:
  conditions:
  - lastProbeTime: null
    status: "True"
    type: Initialized

재밌는 건 last applied configuration이 어떤 파일 형식으로 kubernetes 저장소에 존재하는 것이 아니라, kubernetes 메모리에 있는 live object configuration과 함께 있다는 것이다.

  • live object configuration
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: {"apiVerson":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"run":"myapp-pod","type":"front-end-service"},"name":"myapp-pod"},"spec":{"containers":[{"image":"nginx:1.19","name":"nginx-container"}]}}
  labels:
    app: myapp
spec:
  containers:
    - name: nginx-container
      image: nginx:1.19
status:
  conditions:
  - lastProbeTime: null
    status: "True"
    type: Initialized

위와 같이 실제 kubernetes memory에서는 annotation으로 last applied configuration을 가지고 있는 것이다.

정리하자면 kubectl apply가 동작하면 local file, live object configuration, last applied configuration 3가지를 가지고 비교가 이루어 진다는 것이다.

시험 tip

관리를 할 때는 Declarative 명령어를 사용하는 것이 좋지만, 시험을 볼 때는 Imperative방식이 시간을 절약할 수 있어서 훨씬 좋다.

특히 Imperative 명령어를 사용할 때는, 다음의 두 가지 명령어를 명심하자.

  1. --dry-run: default값은 명령어를 실행하면 kubernetes resource를 생성하지만 --dry-run=client로 실행하면, 이는 resource를 생성하지 않고, 이 명령어로 resource가 생성될 수 있는 지 검사만 하도록 한다.

  2. -o yaml: yaml file 형식으로 resource 정의를 화면에 보여준다.

이 두개를 활용하면 resource에 대한 정의 파일을 아주 빠르게 만들 수 있다. 이렇게 만들어진 정의 파일을 통해 쉽게 resource를 생성, 수정, 삭제하는 것이다.

  • create pod
kubectl run nginx --image=nginx
  • create pod with manifest yaml file
kubectl run nginx --image=nginx --dry-run=client -o yaml
  • create pod with port option
kubectl run nginx --image nginx --port 8080
  • create deployemt
kubectl create deployment --image=nginx nginx
  • create deployment with yaml file
kubectl create deployment --image=nginx nginx --dry-run=client -o yaml
  • create deployment with replicas 4
kubectl create deployment nginx --image=nginx --replicas=4
  • update scale of deployment
kubectl scale deployment nginx --replicas=4
  • create yaml definition
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > nginx-deployment.yaml
  • create service
kubectl expose pod redis --port=6379 --name redis-service --dry-run=client -o yaml

service를 만들되, selector부분의 label은 해당하는 podlabel을 selector로 삼는다. portpod의 port이다. 따라서 redis pod의 port가 되는 것이다. 참고로 targetPort역시도 port와 동일한 값으로 설정된다.

또는 다음과 같이 만들 수 있다.

kubectl create service clusterip redis --tcp=6379:6379 --dry-run=client -o yaml

단, 다음의 경우 selectorlabel부분이 pod의 label로 자동 설정되지 않는다. 대신 app=redis로 selector가 설정된다.

참고로 redis는 service이름이 아니라 pod이름이다. kubectl create방식으로는 service이름을 지정할 수 없음을 알도록 하자.

  • create service of type NodePort
kubectl expose pod nginx --type=NodePort --port=80 --name=nginx-service --dry-run=client -o yaml

expose로 만들었으므로 nginx의 label이 자동으로 selector에 설정된다.

재밌는 것은 NodePort임에도 nodePort를 지정할 수가 없다. 파일을 생성하고 수정하는 방법말고는 없다.

또는 다음과 같은 방법을 사용할 수 있다.

kubectl create service nodeport nginx --tcp:80:80 --node-port=30080 --dry-run=client -o yaml

label selector가 없기 때문에 app=nginx라는 selector label로 자동 설정된다.

service 생성 시 exposecreate 둘 다 어느 하나 하자가 있긴하지만, 그냥 expose를 쓰도록 하자.

0개의 댓글