ReadinessProbe
와 LivenessProbe
는 end user에게 application을 제공하기 위해서는 필수와도 같은 존재이다. ReadinessProbe
를 먼저 구현해보도록 하고, 이를 통해 우리의 container들이 traffic을 받아들일 준비가 되었는 지 아닌 지 보장할 수 있도록 만들어보자.
Readiness probes는 엄밀히 말하자면 service가 아니다. 그러나, service와 긴밀한 관계가 있다.
ReadinessProbe
는 타겟이 되는 pod가 traffic을 받을 준비가 완전히 되었는 지 보장하기 위해 사용한다. 이를 service를 이용하여 확인하는 것이다.
왜 굳이 pod가 traffic을 받을 준비가 되었는 지 확인해야할까?? kubernetes cluster 세상에서는 pod와 service가 분리되어 존재한다. service가 만들어져 client의 요청을 받아도, pod는 죽어있는 상태거나 다시 deploy될 수 있는 상태여서 traffic을 못받을 수 있다는 것이다. 이러한 경우, client는 계속해서 응답을 받을 때까지 대기해있거나 원하던 정보를 얻지 못하고 다음 단계로 나갈 수 있다. 이는 UX 입장에서 좋지 못한 경험이다.
따라서, readiness를 확인하여 pod가 traffic을 받을 수 있는 지 아닌 지를 판단하는 것이다. 이는 추가적인 configuration을 pod에 추가하면 된다.
pod의 readiness probe가 설정되면 control plane
에 해당 pod가 traffic을 받을 준비가 되었다는 signal을 보낸다. 만약, pod가 준비되어있지 않으면 service는 traffic을 전달하지 않는다.
ReadinessProbe
의 구현은 pod의 yaml manifest에 추가 설정을 붙이면 된다. 즉, Service
의 일종이 아니라는 것이다. pod object의 spec
에 configuration을 설정함으로서 kubernetes에게 pod가 완전히 ready되도록 기다려 달라고 요청하고, 완료되면 traffic을 service를 통해 받을 수 있도록 한다.
ReadinessProbe
는 서로 다른 3가지 타입이 있다.
Command
: pod가 ready인지 아닌 지를 판별하기 위해 command을 사용하여 exit code가 0이 나오도록 하겠다는 것이다.HTTP
: HTTP request를 전달하여 200 <= response code < 400 의 응답이 오면 pod가 ready되었다고 판단한다.TCP
: TCP 연결 시도를 만든다. 만약 connection이 성공하면 pod는 ready이다.이제 yaml
파일을 만들어보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-with-readiness-http
spec:
containers:
- name: nginx-pod-with-readiness-http
image: nginx
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 5
httpGet:
path: /ready
port: 80
kind
가 Pod
이다. Service
가 아니다. readinenssProbe
라는 키워드가 추가되었는데, 이 부분을 추가하면 자동으로 kubernetes control plane에서 httpGet.path, httpGet.port
로 요청을 보낸다. 이 때 요청 주기는 처음 시작할 때 initialDelaySeconds
만큼 쉬고, periodSeconds
마다 보낸다. 즉, readiness
가 확인되어도 주기적으로 시간이 될 때마다 계속해서 check를 한다는 것이다.
위의 ReadinessProbe
pod를 만들어보도록 하자.
kubectl create -f nginx-pod-with-readiness-http.yaml
pod/nginx-pod-with-readiness-http created
list를 확인해보면 READY
가 0/1
이다.
kubectl get po nginx-pod-with-readiness-http
NAME READY STATUS RESTARTS AGE
nginx-pod-with-readiness-http 0/1 Running 0 16s
ReadinessProbe
가 구동되고 있지만, 80
포트로 /ready
의 요청이 정상적인 응답으로 오지 않는다는 것이다.
kubectl logs
로 확인해보면 계속해서 control plane에서 /ready
요청을 보내는 것을 알 수 있다.
그럼, /ready
를 라우팅하는 웹 어플리케이션 서버를 하나 만들어서 ReadinessProbe
가 성공하는 지 확인해보도록 하자.
package main
import (
"fmt"
"net/http"
)
func readyHandler(res http.ResponseWriter, req *http.Request) {
fmt.Println("Get ready check")
res.Write([]byte("Success to Ready!"))
}
func healthHandler(res http.ResponseWriter, req *http.Request) {
fmt.Println("Get health check")
res.Write([]byte("Success to health!"))
}
func main() {
app := http.NewServeMux()
app.HandleFunc("/ready", readyHandler)
app.HandleFunc("/health", healthHandler)
http.ListenAndServe(":80", app)
}
다음은 80 port로 서버를 열고 /ready
로 라우팅을 전달하는 간단한 golang 어플리케이션 서버이다. go.mod
까지 만들어주도록 하자.
go mod init test-server
go.mod
가 만들어졌을 것이다.
다음의 Dockerfile
을 사용하여 test_server.go
파일을 빌드하도록 하자.
FROM golang:1.19
WORKDIR app
COPY go.mod ./
COPY *.go ./
RUN go build -o /test_server
EXPOSE 80
CMD ["/test_server"]
다음으로 docker image를 빌드해보도록 하자.
docker build --tag test-server .
다음의 명령어로 빌드 후에는 test-server:latest
docker 이미지가 만들어졌을 것이다. 이를 사용하여 pod를 만들어보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: test-server-readiness-http
spec:
containers:
- name: test-server
image: test-server:latest
imagePullPolicy: IfNotPresent
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 5
httpGet:
path: /ready
port: 80
test-server:latest
를 image로 하는 pod
를 만들 수 있다. imagePullPolicy
가 IfNotPresent
인데, kubernetes는 기본적으로 docker image를 가져올 때 docker hub에서부터 가져온다. 만약 hub에서 안가져오고 local의 image를 쓰고 싶다면 IfNotPresent
옵션을 주어 local에 없으면 docker hub로 부터 가져오도록 하는 것이다.
이제 pod를 만들어보도록 하자.
kubectl create -f ./test-server-readiness.yaml
pod/test-server-readiness-http created
다음으로 ready
에 성공했는 지 확인하도록 하자.
kubectl get pod test-server-readiness-http
NAME READY STATUS RESTARTS AGE
test-server-readiness-http 1/1 Running 0 28s
READY
에 성공한 것을 확인할 수 있다.
로그를 확인하면 다음과 같다.
kubectl logs test-server-readiness-http -f
Get ready check
Get ready check
Get ready check
Get ready check
Get ready check
...
계속해서 ReadinessProbe
가 이루어지고 있음을 알 수 있다.
ReadinessProbe
는 굉장히 중요하다. 해당 application이 확실 traffic을 받을 수 있는 상태인지 확인할 수 있기 때문이다.
그런데, ReadinessProbe
의 한 가지 문제가 있는데, 바로 endpoint을 받을 준비가 되었지만 pod가 제정신이 아닌 경우가 있다. 즉, 정상적으로 동작하지 않는 경우가 있다는 것이다. 이를 위해 설정하는 것이 바로 LivenessProbe
이다.
LivenessProbe
를 통해서 health check를 할 수 있다. 이는 pod가 broken state인지 아닌지 를 결정하는 것으로, 오랫동안 살아있어야 하는 process에 많이 사용된다. 가령 하나의 service에 3개의 pod들이 붙어있다고 생각해보자. pod 중 하나가 broken state에 빠졌지만, readiness check은 true일 수 있다.
[사진5]
이런 경우 pod의 healthy가 정상이 아니기 때문에 해당 pod에 request를 전달하면 안된다. 그러나 readiness만 확인하고 service가 traffic을 흘려보내면 end user는 좋지 않은 경험을 하게되고, 전체 프로그램에 큰 문제가 야기될 수 있다.
때문에, pod가 borken 상태인지 아닌지 확인하는 check가 필요하고 해당 pod를 kill시켜 다시 시작시킬 필요가 있다.
LivenessProbe
가 이러한 문제의 해결책이며, 이는 pod level에서 만들어진다. 단, 주의할 것은 LivenessProbe
는 pod를 고치는 것이 아니라 단순히 비정상적인 pod를 탐지하고 죽이는 명령어를 반복할 뿐이다. 즉, 그냥 처음부터 잘못된 pod는 계속해서 죽고 살고를 반복할 뿐이지 문제가 해결되지 않는다는 것이다.
LivenessProbe
는 healthcheck로 긴 시간동안 주기적으로 application의 상태를 확인하고 추적한다. 즉, 한 번만 check가 확인된다해서 끝나는 것이 아니라, 계속해서 실행된다.
다음의 3가지 type이 존재한다.
Command
: command를 실행하여 container의 상태를 판단한다. exit code가 0이 되어야 healthy이다.HTTP
: pod에 대한 HTTP request를 실행하고, 그 결과가 200 ~ 400 사이의 응답(단, 200은 포함, 400은 미포함)일 경우 healthy하다.TCP
: tcp 연결을 정의하여 connection이 성공하면 pod는 healthy하다.LivenessProbe
를 구현할 때 조심해야할 것은 periodSeconds
와 initialDelaySeconds
를 잘 써줘야 한다는 것이다. periodSeconds
는 주기적으로 healthy체크를 하는 부분이고, initialDelaySeconds
는 초기에 얼마만큼 기다릴 것인지를 의미한다. initialDelaySeconds
가 너무 짧으면 준비가 되지 않은 상태에서 요청을 받아 healthy하지 않다고 판단을 내릴 수 있다.
이전에 만들었던 test_server.go
파일을 통해서 livenessProbe를 해보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: test-server-liveness-http
spec:
containers:
- name: test-server
image: test-server:latest
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 5
httpGet:
path: /health
port: 80
httpHeaders:
- name: My-Custom-Header
value: My-Coustom-Header
딱히 추가된 것은 없다. livenessProbe
로 바뀐 것이 다이다. path
는 /health
로 두었다.
kubectl create -f test-server-liveness-http.yaml
pod/test-server-liveness-http created
생성 후에 로그를 확인하여 /health
요청이 제대로 오고 있는 지 확인하도록 하자.
kubectl logs test-server-liveness-http -f
Get health check
Get health check
Get health check
Get health check
...
Get health check
이 계속 올 것이다. 이처럼 healthcheck는 계속해서 주기적으로 설정한 시간 동안 LivenessProbe
를 한다.
같은 pod에 ReadinessProbe
와 LivenessProbe
를 함께 사용할 수 있다. 이들은 서로 다른 목적을 가지고 있으며, 한 가지가 Okay인 상태여도 다른 한 가지가 아닐 수 있다.
이들은 거의 같은 방식으로 설정되며 위에 우리가 했던 실습과 별반 차이가 없다. 다음의 parameter들을 설정 시에 둘 다 쓰이는 파라미터들이다.
initialDelaySeconds
: 첫번째 probe 실행 이전에 기다려야할 시간periodSeconds
: 다음 probe 주기timeoutSeconds
: timeout 이전에 기다려야할 시간successThreshold
: pod가 ready 또는 healthy인지 판단하기위한 성공횟수failureThreshold
: pod가 not-ready 또는 unhealthy인지 판단하기위한 실패횟수위에서 만든 test-server
를 이용하여 readiness와 liveness를 같이 check해보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: test-server-liveness-http
spec:
containers:
- name: test-server
image: test-server:latest
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 5
httpGet:
path: /health
port: 80
httpHeaders:
- name: My-Custom-Header
value: My-Coustom-Header
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 5
httpGet:
path: /ready
port: 80
다음과 같이 livenessProbe
와 readinessProbe
두 개를 하나의 yaml
파일에 나누어 빌드를 하면 된다.
실행시켜보고 로그를 확인헤보자.
kubectl create -f test-server-readiness-liveness.yaml
pod/test-server-liveness-http created
...
kubectl logs test-server-liveness-http -f
Get health check
Get ready check
Get health check
Get ready check
Get health check
Get ready check
Get health check
Get ready check
로그를 확인해보면 계속해서 ReadinessProbe
와 LivenessProbe
가 구동되고 있는 것을 볼 수 있다. 이와 같이 이들을 같이 사용하는 것은 문제가 없다.
NetwrokPolicy
object는 network firewalls(방화벽)을 cluster에 직접 구현해준다.
kubernetes cluster내의 micro service들은 서로 간의 network 통신으로 의사소통을 할 수 있다. 이 과정에서 특정 pod에 대한 방화벽을 만들어 보안을 집중시키고 싶을 수 있다.
keubernetes에서는 NetworkPolicy
라는 방화벽을 구현하였다. 이는 새로운 resource 종류로 오직 허용된 IP만 받고 그렇지 않은 IP에 대해서는 요청을 제어할 수 있다.
NetworkPolicy
를 사용하는 이점은 다음과 같다.
1. egress/ingress rule을 Classless Inter-Domain Routing(CIDR)
blocks에 기반으로 만들 수 있다.
2. egress/ingress rule을 pod의 label과 selector를 기반으로 만들 수 있다.
3. egress/ingress rule을 namespace에 근거하여 만들 수 있다.
NetworkPolicy
가 동작하기 위해서는 kubernetes cluster에 CNI plugin을 설치해야한다. CNI plugin은 일반적으로 kubernetes에 default로 설치되지 않는다. 만약 minikube
를 사용하고 있다면 Calico
가 기본적으로 설치되어 있을텐데, 일반 kubernetes cluster에도 Calico
를 설치하면 NetworkPolicy
를 지원할 수 있다.
minikube
에서는 다음과 같은 명령어로 가능하다.
minikube start --network-plugin=cni --cni=calico
만약 cloud에서 kubernetes를 사용하고 있다면 cloud 공급자가 제공하는 CNI를 사용하는 것이 좋다.
필자는 local kubernetes cluster에 calio를 설치하여 CNI를 설정하였다.
예제로 두 개의 nginx pod를 만들도록 하자. 하나는 app=nginx-1
label을 가지고 하나는 app=nginx-2
label을 가지도록 한다.
kubectl run nginx-1 --image nginx --labels 'app=nginx-1'
pod/nginx-1 created
kubectl run nginx-2 --image nginx --labels 'app=nginx-2'
pod/nginx-2 created
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-1 1/1 Running 0 26s 192.168.33.195 gy95park-400tca-400sca <none> <none>
nginx-2 1/1 Running 0 19s 192.168.33.229 gy95park-400tca-400sca <none> <none>
kubectl get pods -o wide
로 각 pod의 IP를 알 수 있다. nginx-1
에서 nginx-2
로 curl
을 전송해보도록 하자.
kubectl exec nginx-1 -- curl 192.168.33.229
...
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
...
요청이 잘 전송되었다.
이제 NetworkPolicy
를 만들어 nginx-1
에서 nginx-2
로 network 요청을 보내는 것만 허용하고, 나머지는 막도록 해보자.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx-2-networkpolicy
spec:
podSelector:
matchLabels:
app: nginx-2 # applies to witch pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: nginx-1 # allows calls from which pod
ports:
- protocol: TCP
port: 80
이제 실행해보도록 하자.
kubectl create -f nginx-2-networkpolicy.yaml
networkpolicy.networking.k8s.io/nginx-2-networkpolicy created
kubectl get networkpolicy
NAME POD-SELECTOR AGE
nginx-2-networkpolicy app=nginx-2 10s
잘 만들어진 것을 볼 수 있다.
이제 아까와 같이 nginx-1
pod에서 nginx-2
의 80포트로 요청을 보내보도록 하자.
kubectl exec nginx-1 -- curl 192.168.33.229
...
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
...
성공적으로 보내지는 것을 볼 수 있다.
만약, 다른 port로 요청하면 어떻게 될까? nginx-container
는 80
port 이외에는 열려있지 않기 때문에 다른 port로 요청시에 connection refused와 같이 연결에 실패해야한다. 8080 port로 요청을 보내보도록 하자.
kubectl exec nginx-1 -- curl 192.168.33.229:8080
...
계속 무한 대기하게되는 것을 볼 수 있다. 이는 NetworkPolicy
에서 막았기 때문이다. 즉, nginx-1
입장에서 nginx-2
의 8080
으로 요청을 보냈지만 요청이 막혀서 nginx-1
에게 못닫는 것이다. 따라서, connection refused와 같은 응답도 오지 않고 계속해서 기다리고만 있는 것이다.
그럼 NetworkPolicy
의 port
를 8080
으로 변경해보고 다시 요청해보도록 하자.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx-2-networkpolicy
spec:
podSelector:
matchLabels:
app: nginx-2 # applies to witch pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: nginx-1 # allows calls from which pod
ports:
- protocol: TCP
port: 8080
manifest 파일을 변경하였다면 kubectl apply
로 적용할 수 있다.
kubectl apply -f nginx-2-networkpolicy.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
networkpolicy.networking.k8s.io/nginx-2-networkpolicy configured
이제 nginx-1
에서 nginx-2
의 8080
포트로 요청을 보내보도록 하자.
kubectl exec nginx-1 -- curl 192.168.33.229:8080
...
curl: (7) Failed to connect to 192.168.33.229 port 8080: Connection refused
command terminated with exit code 7
Connection refused
가 발생하였다. 이는 nginx-2
에서 8080
포트를 열지 않았기 때문에 해당 port로 오는 request를 거절한 것이다. 즉, 이전과 달리 nginx-1
에서 nginx-2
의 8080
포트로 요청이 전달되었다는 것이다.
반면, 이전에 응답을 받았던 80
포트로 요청을 보내면 이야기가 달라진다.
kubectl exec nginx-1 -- curl 192.168.33.229:80
...
무한 대기를 하게된다. 이는 방화벽 기능을 하고 있는 NetworkPolicy
에 의해 요청이 막혔기 때문이다.
NetworkPolicy
는 pod간의 보안을 위해 굉장히 효율적인 방화벽 역할을 해주므로 사용하는 것이 좋다. 또한, 일반적으로 방화벽을 설정할 때 CIDR을 기반으로 많이 사용한다. 이는 pod가 외부의 cluster로부터 호출될 때 요청을 제어할 수 있어 좋다. 또는, pod간의 통신을 제어할 때는 label selector나 namespace를 사용하는 것이 좋다.
그런데, namespace가 무엇인가?? 그건 다음 chapter에서 배워보도록 하자.