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 명령어는 두 가지 종류가 있다.
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
에러가 발생한다.
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 명령어 역시도 두 가지 방식을 가지고 있는데, 둘 다 동일한 명령어인 kubectl apply
를 사용한다.
kubectl apply -f nginx.yaml
kubectl apply -f /path/to/config
재밌는 것은 특정 파일 뿐만 아니라, 디렉터리도 지정할 수 있다는 것이다. 디렉토리에 있는 모든 yaml파일들을 실행하게 되는 것이다.
kubectl apply -f nginx.yaml
Declarative
명령어 방식이 좋은 점은 Imperative
방식과는 달리 이전 환경을 고려하지 않아도 된다는 것이다. 가령, pod
를 생성할 때 kubectl create
명령어는 이전에 이미 동일한 pod
가 있다면 에러를 발생시키지만, kubectl apply
는 발생시키지 않고 변화된 부분만 찾아서 적용한다.
반대로 kubectl replace
는 수정하려는 pod가 없으면 에러를 발생시키만, kubectl apply
는 에러를 발생시키지 않고 새로운 pod를 만들어낸다.
따라서, 관리할 때는 kubectl apply
를 사용하는 것이 좋다.
어떻게 kubectl apply
가 내부적으로 동작하는 지 알아보도록 하자.
다음과 같은 kubernetes 설정 파일이 local에 있다고 하자.
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 configuration
에 nginx.yaml
이 메모리로 저장되는데, 상태 관리를 위한 status
가 추가된다.
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파일이다.
{
"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
로 바꾸는 것이다.
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 메모리에 있던 데이터가 바꾸게된다.
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에 적용된다.
{
"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
에서 라벨을 하나 지운다고 하자.
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 file
과 last applied configuration
을 먼저 비교하여 type: front-end-service
가 삭제되었다는 것을 알고, 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
과 함께 있다는 것이다.
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가지를 가지고 비교가 이루어 진다는 것이다.
관리를 할 때는 Declarative
명령어를 사용하는 것이 좋지만, 시험을 볼 때는 Imperative
방식이 시간을 절약할 수 있어서 훨씬 좋다.
특히 Imperative
명령어를 사용할 때는, 다음의 두 가지 명령어를 명심하자.
--dry-run
: default값은 명령어를 실행하면 kubernetes resource를 생성하지만 --dry-run=client
로 실행하면, 이는 resource를 생성하지 않고, 이 명령어로 resource가 생성될 수 있는 지 검사만 하도록 한다.
-o yaml
: yaml file 형식으로 resource 정의를 화면에 보여준다.
이 두개를 활용하면 resource에 대한 정의 파일을 아주 빠르게 만들 수 있다. 이렇게 만들어진 정의 파일을 통해 쉽게 resource를 생성, 수정, 삭제하는 것이다.
kubectl run nginx --image=nginx
kubectl run nginx --image=nginx --dry-run=client -o yaml
kubectl run nginx --image nginx --port 8080
kubectl create deployment --image=nginx nginx
kubectl create deployment --image=nginx nginx --dry-run=client -o yaml
kubectl create deployment nginx --image=nginx --replicas=4
kubectl scale deployment nginx --replicas=4
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > nginx-deployment.yaml
kubectl expose pod redis --port=6379 --name redis-service --dry-run=client -o yaml
service를 만들되, selector
부분의 label은 해당하는 pod
의 label
을 selector로 삼는다. port
는 pod
의 port이다. 따라서 redis
pod의 port가 되는 것이다. 참고로 targetPort
역시도 port
와 동일한 값으로 설정된다.
또는 다음과 같이 만들 수 있다.
kubectl create service clusterip redis --tcp=6379:6379 --dry-run=client -o yaml
단, 다음의 경우 selector
의 label
부분이 pod의 label
로 자동 설정되지 않는다. 대신 app=redis
로 selector가 설정된다.
참고로 redis
는 service이름이 아니라 pod
이름이다. kubectl create
방식으로는 service이름을 지정할 수 없음을 알도록 하자.
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 생성 시 expose
든 create
둘 다 어느 하나 하자가 있긴하지만, 그냥 expose
를 쓰도록 하자.