본문 바로가기

개발 일지

[k8s] minikube 로 k8s 클러스터 실습하기

 

Kubernetes는 컨테이너 오케스트레이션 툴로, 가용성 있는 서비스를 운영하는 데에 적합한 기능을 가지고 있으며, 필자 역시도 직무상 운영하는 서비스에 Kubernetes 를 애용하고 있다. 컨테이너 기반 서비스 운영에 한번 맛을 들이니, Rolling Update, Versioning, 그리고 Scaling 등 유용한 기능이 많아, 계속 Kubernetes 를 사용하게 되었는데, 실제 Node 들을 구성해서 클러스터를 구축하기에는 너무 시간과 노력이 많이 소모되고, EKS 와 같은 클라우드 툴을 사용하자니 비용이 너무 많이 들어서, 여러가지 실습을 해볼 만한 플랫폼이 마뜩치 않았다. 이럴 때 minikube 를 사용하면 간편하게 local 에서 클러스터를 구성할 수 있어서 애용하고 있다.

minikube

사실 minikube는 꽤나 자주 볼 수 있는 툴이다. k8s 공식 문서에서도 학습 환경으로 minikube 를 소개하고 있다. minikube는 기본적으로 docker in docker 스타일로 local 에서 k8s (kubernetes 가 k와 s 사이에 8 글자가 있다고 해서 k8s라고 줄여 부른다.) 클러스터를 구성하는데, 다시 말해 도커 컨테이너 하나를 node 하나라고 생각하고 클러스터를 구성할 수 있다.

minikube 를 설치하고 클러스터를 실행하는 방법은 공식 문서 를 따라하면 금방 할 수 있다. 먼저 minikube 를 설치한다. 필자는 OSX 위에서 작업을 했기 때문에, homebrew 를 이용해서 설치했다.

brew install minikube

도커 기반으로 작동하기 때문에, 당연히 docker daemon 이 실행되고 있어야 한다. docker 가 실행 중인 상태에서 기본 설정으로 클러스터를 실행하려면 다음 커맨드를 실행하면 된다.

minikube start

그러면 minikube 라는 이름의 컨테이너 하나가 생성되는 것을 볼 수 있다.

minikube 컨테이너가 실행된다

kubectl 역시 사용할 수 있는데, 만약 kubectl 명령어를 찾지 못한다면, minikube kubectl 명령어를 사용할 수 있다. 편의상 ~/.bashrc 같은 파일에

alias kc="minikube kubectl --"

와 같이 추가해 두면, 좀 더 편리하게 사용할 수 있다.

Kubernetes Dashboard 역시 사용할 수 있다.

minikube dashboard

를 실행하면, k8s dashboard 를 열 수 있다.

대시보드를 볼 수 있다

실행 중인 컨테이너를 완전히 삭제하기 위해서는

minikube delete

를 실행하면 된다. 실행 후 컨테이너가 완전히 삭제되었는지 확인하자.

minikube 클러스터 구성

minikube 는 노드 한 대를 이용해서도 control plane 과 data plane 를 동시에 구성할 수 있다. 하지만 노드 한 대로는 재미가 없으므로, control plane 노드 한 대와, worker node 2대를 구성해 보자. minikube 로 구성할 클러스터는 다음과 같다.

Worker node 2대와 Control Plane

 

다음 커맨드를 실행해서 3개의 컨테이너를 생성한다.

minkube start --nodes 3

클러스터 생성이 완료되고, 노드를 확인해보면, control-plane 이라는 role 을 가진 node 한 대와 추가 node 2대가 생성된 것을 확인할 수 있다.

kubectl get nodes

를 실행하면, 다음과 유사한 결과가 나와야 한다.

NAME           STATUS   ROLES           AGE   VERSION
minikube       Ready    control-plane   55s   v1.27.3
minikube-m02   Ready    <none>          33s   v1.27.3
minikube-m03   Ready    <none>          12s   v1.27.3

minikube 라는 이름의 node 가 control-plane 이고, minikube-m02 와 minikube-m03 을 worker node 로 사용할 것이다.

여기까지 하면 cluster 가 준비된 것이지만, 몇 가지 설정을 더 해보자. 먼저, nginx-ingress-controller를 사용하기 위해서 minikube addon 을 활성화 해보자.

minikube addons enable ingress

명령어 실행이 끝나고 ingress-nginx 라는 네임스페이스의 pod 을 확인해 보면

kubectl get pods -n ingress-nginx

다음과 같이 pod 가 실행중이어야 한다.

NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-rkwnd        0/1     Completed   0          42s
ingress-nginx-admission-patch-pqb7k         0/1     Completed   0          42s
ingress-nginx-controller-7799c6795f-mkdzz   1/1     Running     0          42s

다음으로, 우리는 minikube node 를 control plane 으로 설정하기로 했으므로, 해당 node 에는 pod 가 생성되지 못하게 설정해 보자. minikube node 에 taint 를 설정해 준다.

kubectl taint nodes minikube node-role.kubernetes.io/control-plane=:NoSchedule

여기까지 하면, 실습을 진행하기 위한 클러스터는 준비되었다.

클러스터 위에 nginx 와 httpd 서버 배포하기

잘 구성된 우리의 클러스터 위에, nginx 와 httpd 서버를 배포해 보자. 실습을 마치면 구성될 아키텍쳐는 다음과 같다.

nginx 와 httpd 배포

 

k8s ingress 를 통해서, /nginx path 로 들어오는 요청은 nginx 서버로 라우팅하고, /httpd 로 들어오는 요청은 httpd 서버로 라우팅할 계획이다. 먼저, nginx 와 httpd 서버를 실행할 pod 들을 배포해 보자.

Deployment 생성

nginx-deployment.yaml 과 http-deployment.yaml 파일을 생성하고 다음과 같이 작성한다.

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-app
  labels:
    app: nginx
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.25
          ports:
            - name: http
              containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "250m"
            limits:
              memory: "128Mi"
              cpu: "500m"
# httpd-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd-app
  labels:
    app: httpd
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
        - name: httpd
          image: httpd:2.4
          ports:
            - name: http
              containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "250m"
            limits:
              memory: "128Mi"
              cpu: "500m"

 

배포를 위해서 다음 명령어를 실행한다.

kubectl create -f nginx-deployment.yaml
kubectl create -f httpd-deployment.yaml

 

명령어 실행이 완료되고, pod 가 생성될 때까지 잠시 기다리면, 다음과 같이 pod 6개가 생성되어야 한다.

kubectl get pods -o wide

를 실행하면,

NAME                         READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
httpd-app-7d579f65c-6szxg    1/1     Running   0          21s   10.244.1.3   minikube-m02   <none>           <none>
httpd-app-7d579f65c-7xxp7    1/1     Running   0          21s   10.244.2.4   minikube-m03   <none>           <none>
httpd-app-7d579f65c-9fs9q    1/1     Running   0          21s   10.244.1.4   minikube-m02   <none>           <none>
nginx-app-55f4f85675-4gzmv   1/1     Running   0          25s   10.244.1.2   minikube-m02   <none>           <none>
nginx-app-55f4f85675-fcjv5   1/1     Running   0          25s   10.244.2.3   minikube-m03   <none>           <none>
nginx-app-55f4f85675-twkdk   1/1     Running   0          25s   10.244.2.2   minikube-m03   <none>           <none>

 

nginx pod 가 3개, httpd pod 가 3개씩 생성되었고, 모두 우리가 설정한 worker node 위에 생성된 것을 확인할 수 있다. 실제로 pod 이

잘 실행되는지 확인해 보기 위해서 직접 요청을 보내보자. 위 출력에서 IP 주소는 클러스터 내부에서만 유의미한 IP 이기 때문에, master node 로 ssh 해서 접속한 후 httpd pod 중 하나의 IP 주소로 요청을 보내보자. minikube 에서 노드로 ssh 할 수 있는 기능을 제공한다.

minikube ssh

접속이 완료되면 요청을 보내 보자.

curl 10.244.1.3

그러면 다음과 같이 응답이 잘 와야 한다.

<html><body><h1>It works!</h1></body></html>

exit 명령어를 통해 ssh 에서 빠져나올 수 있다.

exit

Service 생성

이제 pod 에 직접 요청을 보내는 것이 아니라 service 를 통해 pod 에 요청을 전달할 수 있도록, ClusterIP Service 를 각각 생성해 보도록 하자.

nginx-svc.yaml 과 httpd-svc.yaml 파일을 생성하고 다음과 같이 작성한다.

# nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 8080
      targetPort: http
# httpd-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: httpd
spec:
  type: ClusterIP
  selector:
    app: httpd
  ports:
    - protocol: TCP
      port: 3000
      targetPort: http

 

해당 service 들을 생성한다.

kubectl create -f nginx-svc.yaml
kubectl create -f httpd-svc.yaml

 

생성된 service 들을 확인해보기 위해 다음과 같이 명령을 입력하면

kubectl get svc

 

생성한 service 들을 확인할 수 있다.

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
httpd        ClusterIP   10.108.190.145   <none>        3000/TCP   3s
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    31m
nginx        ClusterIP   10.107.207.191   <none>        8080/TCP   7s

 

kubernetes 라는 이름의 서비스는 기본으로 생성되어 있는 것이기 때문에 신경쓰지 않아도 된다. post 3000번으로 요청하면 httpd pod 로 요청을 전달하는 httpd 서비스와 port 8080번으로 요청하면 nginx pod 로 요청을 전달하는 nginx 서비스를 생성했다. 무작위로 고른 port 이지만, 주로 로컬 개발시에 사용하는 port 들이라서 서로 다르게 구성해보았다.

service 로 요청을 보내서 응답이 제대로 오는지 확인하자. 다시 master node 로 접속한 후, httpd 의 cluster ip 로 요청을 보내보자.

minikube ssh
curl 10.108.190.145:3000

위에서 했던 것과 마찬가지로 응답이 잘 와야 한다.

Ingress 생성

이제 외부 요청을 path 에 따라 알맞은 서비스로 라우팅해 줄 수 있도록 Ingress 를 생성해 보자.

nginx-ingress.yaml 파일과 httpd-ingress.yaml 파일을 생성하고 다음과 같이 작성한다.

# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /nginx
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 8080
# httpd-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpd-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /httpd
            pathType: Prefix
            backend:
              service:
                name: httpd
                port:
                  number: 3000

 

/nginx path 로 요청이 오면, nginx 서비스의 포트 8080의 path / 로 전달하고, /httpd 역시 마찬가지로 알맞게 전달하도록 설정했다. 해당 ingress 를 생성해 보자.

kubectl create -f nginx-ingress.yaml
kubectl create -f httpd-ingress.yaml

 

Ingress 를 확인하기 위해서 다음과 같이 실행하면

kubectl get ingress

 

두 개의 ingress 가 생성된 것을 바로 확인할 수 있다.

NAME            CLASS   HOSTS   ADDRESS   PORTS   AGE
httpd-ingress   nginx   *                 80      9s
nginx-ingress   nginx   *                 80      14s

 

우리가 ingressClassName 을 nginx 로 설정함으로써 nginx-ingress-controller를 통해서 ingress 를 구성하기로 했는데, ingress 요청이 서비스로 전달되는 과정을 살펴보자. ingress-nginx 네임스페이스의 서비스를 확인해 보면

$ kc get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.98.132.246    <none>        80:31547/TCP,443:30043/TCP   33m
ingress-nginx-controller-admission   ClusterIP   10.104.149.253   <none>        443/TCP                      33m

 

이렇게 ingress-nginx-controller 로 향하는 NodePort 타입의 서비스가 생성되어 있음을 확인할 수 있다. master node 로 접속해서 해당 서비스의 IP 주소로 요청을 보내보면, 우리가 ingress 를 설정한 대로 라우팅이 되는 것을 확인할 수 있다.

$ minikube ssh
$ curl 10.98.132.246/nginx
<!DOCTYPE html>
<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>

$ curl 10.98.132.246/httpd
<html><body><h1>It works!</h1></body></html>

 

서비스가 NodePort 이기 때문에 master node 에서 해당 포트로 바로 요청을 보내도 같은 결과를 얻을 수 있다.

$ minikube ssh
$ curl localhost:31547/nginx
<!DOCTYPE html>
<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>

$ curl localhost:31547/httpd
<html><body><h1>It works!</h1></body></html>

 

Ingress 는 결국 클러스터 밖에서 들어오는 요청을 핸들링 해 주어야 그 빛을 발할 수 있는데, cluster 안으로 접속하지 않아도 호스트 머신에서 클러스터로 요청을 보낼 수 있도록 minikube tunnel 명령어를 사용해 보자.

minikube tunnel

 

명령어를 실행한 상태로 브라우저에 각각 http://localhost/nginxhttp://localhost/httpd 를 입력해 보면, 다음과 같이 클러스터 밖에서도 pod 에서 응답을 받을 수 있다.

 

http://127.0.0.1/httpd 에서 페이지를 볼 수 있다

 

http://127.0.0.1/nginx 에서도 nginx 페이지를 볼 수 있다

 

결론

minikube 를 이용해서, k8s 배포에 필요한 여러가지 실습과 실험들을 해볼 수 있다. addon 으로 여러가지 기능들을 제공하니, k8s 를 공부하는 데에도 유용하게 사용할 수 있을 것이다.

Appendix

전체 코드는 다음에서 확인할 수 있습니다.

 

GitHub - k2sebeom/minikube-k8s-demo: Demo projects for k8s on minikube

Demo projects for k8s on minikube. Contribute to k2sebeom/minikube-k8s-demo development by creating an account on GitHub.

github.com