kubeadm을 활용하여 웹서비스 배포해보기

1. kubeadm 소개 & 인프라 구성

kubeadm은 쿠버네티스가 제공하는 클러스터를 빠르게 구축할수 있도록 도와주는 도구입니다. kubeadm 외에도 로컬환경에서 간단하게 쿠버네티스를 구축해볼수 있는 minikube, 클라우드 프로비저닝을 기본으로 제공해주는 kops 등이 있지만, 이번 실습에서는 클라우드를 사용하지 않을예정이어서 kubeadm을 선택했습니다.

쿠버네티스 클러스터 구성시 권장사항은 각 노드당 듀얼코어, 2GB 메모리를 갖추는것이지만 학습목적이므로 크게 신경쓰지 않았습니다. 마침 친구에게 받은 라즈베리파이가 있어서 이를 worker-node, 개인 노트북을 master-node로 사용하고 집에서 쓰던 공유기를 라우터로 활용하여 쿠버네티스 클러스터를 위한 내부망을 구성했습니다.

제 PC는 윈도우 운영체제를 사용하고 있지만 kubeadm는 macos나 windows를 공식적으로 지원하지 않으므로 hyper -v 가 지원하는 가상화기술을 사용하여 ubuntu 18.04 기반의 가상머신을 구축하여 master-node로 사용했습니다.

2. 컨테이너 계층 구성

서비스 운영을 위해 web server, web application server, database의 3계층으로 구성해보았습니다. web server replicaset의 경우  NodePort Service를 사용하여 사용자들로부터 request를 직접받아 static, media 파일을 처리할수 있도록 설계해 봤습니다.

web server가 dynamic한 처리가 필요할때는 request를 was replicaset으로 전달할수 있는 매개체와 web server replicaset 역시 Database에 접근하기 위한 매개체가 필요하므로 clusterIP service를 하나씩 생성해주었습니다.

사실 모든 pod들은 클러스터 내부에서 할당받은 IP로 접근하면 되므로 service 객체들이 필요없다고 생각할수 있겠지만, service 객체를 사용하면 내부 DNS의 도움을 받을수 있고 로드밸런싱 기능을 지원하기 때문에 사용하는것이 좋습니다.

 

3. master, worker 노드에 docker 설치하기

kubernetes 를 설치하는데 앞서서 docker-engine의 설치가 필요합니다. kubernetes는 OCI 조건을 만족하는 컨테이너  서비스를 만족한다면 어떤 서비스를 설치해도 상관없지만 제가 잘하는 docker를 쓰기로 했습니다. docker 설치를 위한 스크립트를 아래와 같이 정리했지만 공식 메뉴얼을 보고 설치하는것도 추천드립니다.

(링크:  https://docs.docker.com/engine/install/ubuntu/ )

sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88

docker 설치하는 과정에서 라즈베리파이와 제 개인 PC의 필요한 명령어가 약간 다른 구간이있습니다. CPU의 아키텍쳐가 다르기때문으로 확인하고 아키텍쳐와 일치하는 명령어를 사용해야합니다.

# raspberry pi (armhf)
sudo add-apt-repository \
  "deb [arch=armhf] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"

# PC (x86_64/amd64)
sudo add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"

마지막으로 다음 명령어를 사용하여 도커 엔진과 클라이언트을 설치 해주고 docker group을 생성하여 권한을 설정해주면 끝납니다.

sudo groupadd docker
sudo usermod -aG docker $USER

 

4. master, worker 노드에 쿠버네티스 환경 설치하기

쿠버네티스 환경을 위해서는 모든 노드의 swap 기능를 먼저 멈춰야 한다. swap기능을 모두 멈춘다음에 다음 스크립트를 실행하면 kubeadm을 포함한 kubernetes의 운영에 필요한 모듈들을 설치할수 있습니다. 설치에 필요한 스크립트를 정리해두긴 했지만 다음 공식문서를 보고 설치하는것을 권장합니다.

( 링크: https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ )

# swap 비활성화
swapoff -a

# kubernetes 설치
sudo apt-get update
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

5. 쿠버네티스 클러스터 생성하기

master node 에서sudo kubeadm init --pod-network-cidr=10.244.0.0/16 명령어를 통해 클러스터를 생성할수 있습니다. pod network로 10.244.0.0/16 블록을 사용하는 이유는 이후 설치할 CNI인 Flannel의 기본 pod network 블록이기 때문입니다. 만약 아래의 화면이 뜨면 클러스터가 정상적으로 생성된것입니다.

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.0.8:6443 --token nb7nfw.yaywk4lpag2klkxz \
    --discovery-token-ca-cert-hash sha256:965d27bd44661965523cad524ccbb26f63a7b4cd58ec1203ab8b042ca6450eca

위 과정을 진행할때 라즈베리파이에서 cgroup-xxxxx missing 관련 에러가 뜨는 현상이 있어서 관련자료를 찾아보았는데 아래의 문서의 prep.sh를 보고 해결할수 있었습니다. 명령어로도 정리해보았고 실행해준다음 라즈베리 파이를 재시작 하고 kubeadm 초기화를 하면 해결됩니다.

( https://gist.github.com/alexellis/fdbc90de7691a1b9edb545c17da2d975 )

sudo cp /boot/cmdline.txt /boot/cmdline_backup.txt
orig="$(head -n1 /boot/cmdline.txt) cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"
echo $orig | sudo tee /boot/cmdline.txt

 

kubeadm init에 성공하면 화면에 보이는 3줄짜리 스크립트를 실행시켜주고 pod 간에 통신을 위한 대표적인 네트워크 플러그인인 Flannel를 설치해줍니다. 그리고 성공화면의 맨 마지막줄에 있는 kubeadm join으로 시작하는 구문은 worker node에서 사용해야 하므로 메모장에 킵해둡시다.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Flannel install
kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml

Flannel 까지 정상적으로 설치가 되었으면 sudo kubectl get nodes -n kube-system 명령어를 통해 쿠버네티스 pod들이 전부 정상적으로 running되어 있는지 확인합니다. 모든 pod들의 status가 Running이고 Ready가 1/1 이면 클러스터가 정상적으로 구축되었고 master node에서 해야할일은 끝났습니다.

NAME                                  READY   STATUS              RESTARTS   AGE
coredns-66bff467f8-4nw7q              1/1     Running             0          116s
coredns-66bff467f8-6p8ck              1/1     Running             0          116s
etcd-kube-master                      1/1     Running             0          2m5s
kube-apiserver-kube-master            1/1     Running             0          2m5s
kube-controller-manager-kube-master   1/1     Running             0          2m5s
kube-flannel-ds-amd64-l7lrv           1/1     Running             0          33s
kube-proxy-7zw6t                      1/1     Running             0          116s
kube-scheduler-kube-master            1/1     Running             0          2m5s

이제 여러분의 worker node만 생성된 클러스터에 join시키면 끝납니다. 메모장에 keep 해두었던 명령어를 worker node에서 실행시켜줍니다. 그리고 master node에서 sudo kubectl get nodes, sudo kubectl get pods -n kube-system 을 실행시켜서 두개의 노드와 pod들이 잘실행되는지 확인한후 우리가 사용할 네임스페이스 오브젝트를 하나 만들어주면 쿠버네티스 클러스터 구축이 완료됩니다.

# sudo kubectl get nodes
NAME          STATUS   ROLES    AGE   VERSION
kube-master   Ready    master   15m   v1.18.3
ubuntu        Ready    <none>   12m   v1.18.3

# sudo kubectl get pods -n kube-system
coredns-66bff467f8-x5vfs              1/1     Running   0          72s
coredns-66bff467f8-x9vdk              1/1     Running   0          72s
etcd-kube-master                      1/1     Running   0          14m
kube-apiserver-kube-master            1/1     Running   0          14m
kube-controller-manager-kube-master   1/1     Running   0          14m
kube-flannel-ds-amd64-l7lrv           1/1     Running   0          13m
kube-flannel-ds-arm-xbd55             1/1     Running   0          12m
kube-proxy-7zw6t                      1/1     Running   0          14m
kube-proxy-cqrl2                      1/1     Running   0          12m
kube-scheduler-kube-master            1/1     Running   0          14m

# namespace 생성
kubectl create namespace production

 

6. Database Layer

database layer는 postgres 기반으로 동작할수 있게 설계했습니다. 배포에 필요한 deployment와 clusterip service를 yaml 형식으로 다음과 같이 작성했습니다.

# deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: knufestival2019-db
  namespace: production
spec:
  selector:
    matchLabels:
      app: knufestival2019-db
  template:
    metadata:
      name: knufestival2019-db
      labels:
        app: knufestival2019-db
    spec:
      containers:
      - name: knufestival2019-db
        image: postgres:12.2-alpine
        envFrom:
        - secretRef:
            name: knufestival2019-db-env
# service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: knufestival2019-db-service
  namespace: production
spec:
  ports:
    - name: knufestival2019-db-port
      port: 5432
      targetPort: 5432
  selector:
    app: knufestival2019-db
  type: ClusterIP

 

was 계층은 내부 DNS의 도움으로 위의 service.yaml에 명시된 metadata.name인 knufestival2019-db-service를 host로 접근하여 데이터베이스에 접근할수 있게 됩니다. 우선 docker hub를 참고하여 필요한 환경변수를 secret으로 만들고 아래의 명령어로 deployment와 service를 배포합니다.

# 시크릿 생성
kubectl create secret generic knufestival2019-db-env --from-env-file=.env -n production

# 데이터 베이스 레이어 배포
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# 정상적으로 배포되었는지 확인
kubectl get pods -n production
kubectl get services -n production

7. web application server Layer

was의 경우 쿠버네티스가 제공해주는 replicaset을 사용하여 두개의 컨테이너로 구성해 가용성을 높혀보았습니다. 처음 was를 구동시키면 database에 테이블이 아무것도 없는 상황이기 때문에 정상 작동이 불가합니다. 이를 해결하기 위해서 쿠버네티스가 제공하는 Job 오브젝트를 사용해 migrate를 진행하는 임시 컨테이너를 사용했습니다. Job 오브젝트의 경우 컨테이너에 정의된 명령이 끝나면 자동으로 종료되어 사용자가 힘들게 지울 수고를 덜어줍니다. database layer와 마찬가지로 배포에 필요한 deployment와 clusterip service를 yaml 형식으로 다음과 같이 작성했습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: knu-festival-was
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: knu-festival-was
  template:
    metadata:
      labels:
        app: knu-festival-was
    spec:
      containers:
      - name: web
        image: lunacircle4/knufestival2019:1.0.0
        envFrom:
        - secretRef:
            name: knufestival2019-was-env
---
apiVersion: batch/v1
kind: Job
metadata:
  name: knu-festival-migrator
  namespace: production
spec:
  template:
    spec:
      containers:
      - name: knu-festival-migrator
        image: lunacircle4/knufestival2019:1.0.0
        command: ["/bin/sh"]
        args: ["-c", "python3 manage.py migrate --settings=config.environments.production;"]
        envFrom:
        - secretRef:
            name: knufestival2019-was-env
      restartPolicy: Never
  backoffLimit: 4

web server 계층은 내부 DNS의 도움으로 위의 service.yaml에 명시된 metadata.name인 knufestival2019-was-service를 host로 접근하여 was 계층에 접근할수 있게 됩니다. 장고 배포에 필요한 환경변수를 secret (데이터베이스에 접근하기 위핸 host, password, user 정보 등등이 필요합니다) 으로 만들고 아래의 명령어로 deployment와 service를 배포합니다.

# 시크릿 생성
kubectl create secret generic knufestival2019-was-env --from-env-file=.env -n production

# 데이터 베이스 레이어 배포 
kubectl apply -f deployment.yaml 
kubectl apply -f service.yaml 

# 정상적으로 배포되었는지 확인 
kubectl get pods -n production 
kubectl get services -n production

 

8. web server Layer

web server layer도 was와 유사하게 쿠버네티스가 제공해주는 replicaset을 사용하여 두개의 컨테이너로 구성해 가용성을 높혔습니다. web server layer는 다른 계층과 다르게 NodePort Service의 생성이 필요합니다. 쿠버네티스 클러스터 내부에선 사용할수 있는 ClusterIP Service와 달리 NodePort Service를 활용하면 엔드 유저의 request를 직접 handle 할수 있습니다. database was layer와 마찬가지로 배포에 필요한 deployment와 clusterip service를 yaml 형식으로 다음과 같이 작성했습니다.

# deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: knufestival2019-web
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: knufestival2019-web
  template:
    metadata:
      name: knufestival2019-web
      labels:
        app: knufestival2019-web
    spec:
      containers:
      - name: knufestival2019-web
        image: lunacircle4/knufestival2019-nginx:latest
        volumeMounts:
        - name: knufestival2019-web-conf
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
      volumes:
      - name: knufestival2019-web-conf
        configMap:
          name: knufestival2019-web-conf
# service
apiVersion: v1
kind: Service
metadata:
  name: knufestival2019-web-service
  namespace: production
spec:
  ports:
    - name: knufestival2019-web-port
      port: 80
      targetPort: 80
  selector:
    app: knufestival2019-web
  type: NodePort

쿠버네티스는 configMap이라는 환경변수를 관리할수 있는 오브젝트를 제공합니다. 암호화 되어 저장되지 않는점에서 secret 오브젝트와 차이점이 있고 일반적인 환경변수 뿐만아니라 파일도 configMap으로 관리할수 있는것이 특징입니다. 저는 configMap을 활용하여 nginx.conf 파일을 configMap으로 만들고 web server에서 기본설정으로 사용할수 있도록 바인딩 시켰습니다. 제가 작성한 nginx.conf 파일과 configMap 생성과 web server 배포에 필요한 명령어를 아래에 정리해보았습니다.

# nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  192.168.0.5;

        location /static {
            root /;
        }

        location /media {
            root /;
        }

        location / {
            proxy_pass http://knufestival2019-was-service;
        }
}
# 파일로부터 configMap 생성
kubectl create configmap knufestival2019-web-conf --from-file=nginx.conf -n production

# 데이터 베이스 레이어 배포 
kubectl apply -f deployment.yaml 
kubectl apply -f service.yaml 

# 정상적으로 배포되었는지 확인 
kubectl get pods -n production 
kubectl get services -n production

아래의 명령어를 사용하여 NodePort가 라즈베리파이와 PC의 어떤 포트에 바인딩 되었는지 확인하고 웹브라우저로 접속해봅니다.

# port 확인
kubectl get services -n production

# knufestival2019-web-service 는 32181번으로 NodePort가 설정되어있다.
NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
knufestival2019-db-service    ClusterIP   10.110.95.252   <none>        5432/TCP       6h48m
knufestival2019-was-service   ClusterIP   10.98.124.18    <none>        80/TCP         80m
knufestival2019-web-service   NodePort    10.100.63.118   <none>        80:32181/TCP   76m

9. 회고

라즈베리파이와 개인PC를 연동하여 클러스터를 구성하면서 정신이 아찔했던 순간이 있었습니다. 두 컴퓨팅 자원의 CPU 아키텍쳐가 다르다는점을 간과한것입니다. PC에서 잘돌아가는 이미지가 라즈베리파이에서는 아키텍쳐가 달라 오류가 발생할 확률이 매우(?) 높으므로 클러스터를 구성할때는 동일한 CPU 아키텍처를 사용하는 컴퓨터들을 사용하는것을 권장드립니다.

현재 데이터베이스 레이어의 경우 컨테이너가 다운되면 자료가 모두 날라가는 결함이 있습니다. 다음번에는 퍼시스턴스 볼륨 오브젝트을 활용하여 해결해보고 싶고, 데이터베이스를 마스터, 슬레이브로 구성하여 read replica하는 작업도 다음번에 해보고 싶은 생각이 듭니다. 이번엔 야매로 쿠버네티스 클러스터를 구성해보았지만 다음번엔 kops 등을 활용하여 클라우드상에 클러스터를 구성해보고 오토스케일링 같은 재미있는 주제도 다뤄보고 싶습니다.

이번에 인생 최초로 쿠버네티스라는 서비스를 공부하고 클러스터를 구성해보았는데, 도커를 공부하고 활용하면서 부족하다고 생각했던 기능들이 쿠버네티스에 몽땅 들어가 있어서 놀라웠고, CI/CD 파이프라인을 구성할때 큰 도움을 받을수 있다고 생각했습니다. 다만 공부해야할 내용이 생각보다 많아서 소규모의 시스템을 운영할때는 배보다 배꼽이 더커지는 격이 될수 있을것 같습니다.

긴글 읽어주셔서 감사합니다!

10. 참고자료

<kubernetes>

https://kubernetes.io/docs/concepts/configuration/secret/

https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

https://kubernetes.io/docs/concepts/workloads/controllers/job/#writing-a-job-spec

<nginx>

http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header

https://hub.docker.com/_/nginx

https://stackoverflow.com/questions/34326704/how-do-i-delete-virtual-interface-in-linux/34326753

<gunicorn>

https://docs.gunicorn.org/en/stable/settings.html

<raspberry pi>

https://gist.github.com/alexellis/fdbc90de7691a1b9edb545c17da2d975

(아 힘들다…)

0 0 votes
Article Rating
Subscribe
Notify of
guest

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Scroll to top
0
Would love your thoughts, please comment.x
()
x