Posts Kubernetes(minikube)를 이용하여 웹 애플리케이션 운영하기
Post
Cancel

Kubernetes(minikube)를 이용하여 웹 애플리케이션 운영하기

Kubernetes란

실험환경

  • 목표
    • simple-locker를 minikube를 사용하여 배포해보고
      CI / CD 파이프라인에 연동해본다.
  • 가정
    • 단일 노드에서 배포하는 것을 가정한다.
    • 노드 os는 ubuntu 22.04 이다.
    • 배포하고자 하는 웹 애플리케이션은 simple-locker이다.
  • simple-locker의 구조
  • kubernetes 매니페스트 파일
  • 매니페스트 파일
    • 매니페스트란
      • 설정들이 기록된 yaml 파일들을 말한다.
      • 쿠버네티스에서 yaml 파일을 보고 환경을 동기화시킨다.
    • 사용하는 매니페스트 목록
      • config.yml
      • secret.yml
      • sqlite3-pv.yml
      • sqlite3-pvc.yml
      • webhook-endpoints.yml
      • webhook-svc.yml
      • cluster-issuer.yml
      • simple-api.yml
      • simple-web.yml
      • ingress.yml

Manifest: config.yml

  • 설명
    • configMap
    • 모든 서비스에서 참조할 수 있는 컨피그맵이다.
  • config.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      apiVersion: v1
      kind: ConfigMap
      metadata:
      name: simple-api
      data:
      useK8s: "yes"
      frontendUrlList: "https://your-frontend-url.com,https://www.your-frontend-url.com"
      ginMode: "release"
      ---
      apiVersion: v1
      kind: ConfigMap
      metadata:
      name: simple-web
      data:
      backendOrigin: "your-backend-url.com"
      frontendOrigin: "your-frontend-url.com"
    

Manifest: secret.yml

  • 설명
    • Secret
    • 모든 서비스에서 참조할 수 있는 시크릿이다.
    • 컨피그와 다른 점은 암호화되어 저장된다.
    • 개별 시크릿 크기는 1MiB로 제한된다.
    • 시크릿 내용은 base64로 인코딩되어 있어야 한다.
      ex) echo “my-secret” | base64
  • secret.yml
    1
    2
    3
    4
    5
    6
    7
    8
    
      apiVersion: v1
      kind: Secret
      metadata:
      name: simple-api-secret
      type: Opaque
      data:
      identityKey: your-identityKey
      authSecretKey: your-authSecretKey
    

Manifest: sqlite3-pv.yml

  • 설명
    • persistent-volumes
    • pod는 stateless하기 때문에 pod의 저장소에 sqlite3를 저장하여
      운영할 수 없다.
    • 그래서 PV(persistance volume)을 할당하고 이를 서비스 배포하여
      다른 서비스에서 이 볼륨을 참조할 수 있도록 한다.
    • 여기서 volume이 hostPath를 갖는데
      이는 호스트노드의 일정 경로에 PV을 저장하겠다는 의미이다.
      hostpath
    • 실험 환경이 단일 노드이기에 가능한 방법이며
      실제 production 환경에서는 nfs 등의 볼륨으로 바꿔야할 것이다.
      nfs
  • sqlite3-pv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      apiVersion: v1
      kind: PersistentVolume
      metadata:
      name: sqlite3-pv-volume
      labels:
          type: local
      spec:
      storageClassName: manual
      capacity:
          storage: 500Mi
      accessModes:
          - ReadWriteMany
      hostPath:
          path: "/mnt/data"
    

Manifest: sqlite3-pvc.yml

  • 설명
  • sqlite3-pvc.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
      name: sqlite3-pv-claim
      spec:
      storageClassName: manual
      accessModes:
          - ReadWriteMany
      resources:
          requests:
          storage: 500Mi
    

Manifest: webhook-endpoints.yml

  • 설명
    • Endpoints API
    • endpoints
    • 엔드포인트를 설정하여
      서비스에서 해당 엔드포인트를 참조할 수 있게 한다.
  • webhook-endpoints.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      apiVersion: v1
      kind: Endpoints
      metadata:
      name: webhook-svc
      subsets:
          - addresses:
              - ip: your-webhook-ip
              ports:
              - port: 9000
    

Manifest: webhook-svc.yml

  • 설명
    • webhook-svc 엔드포인트와 연결되는 서비스이다.
    • 서비스가 pod와 연결될때
      실제로는 중간에 엔드포인트를 통한다고 한다.
      [Kubernetes] Endpoints란
    • 여기서는 webhook url을 엔드포인트에 직접 등록하므로써
      각 서비스에서 웹훅 URL로 접근할 수 있게 설정하였다.
  • webhook-svc.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      apiVersion: v1
      kind: Service
      metadata:
      name: webhook-svc
      spec:
      ports:
          - protocol: TCP
          port: 80
          targetPort: 9000
    

Manifest: cluster-issuer.yml

  • 설명
  • cluster-issuer.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
      apiVersion: cert-manager.io/v1
      kind: Issuer
      metadata:
      name: letsencrypt-staging
      spec:
      acme:
          # The ACME server URL
          server: https://acme-staging-v02.api.letsencrypt.org/directory
          # Email address used for ACME registration
          email: your-email@gmail.com
          # Name of a secret used to store the ACME account private key
          privateKeySecretRef:
          name: letsencrypt-staging
          # Enable the HTTP-01 challenge provider
          solvers:
          - http01:
              ingress:
              class:  nginx
      ---
      apiVersion: cert-manager.io/v1
      kind: Issuer
      metadata:
      name: letsencrypt-prod
      spec:
      acme:
          # The ACME server URL
          server: https://acme-v02.api.letsencrypt.org/directory
          # Email address used for ACME registration
          email: your-email@gmail.com
          # Name of a secret used to store the ACME account private key
          privateKeySecretRef:
          name: letsencrypt-prod
          # Enable the HTTP-01 challenge provider
          solvers:
          - http01:
              ingress:
              class: nginx
    

Manifest: simple-api.yml

  • 설명
  • simple-api.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    
      # deployment 설정
      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: simple-api
      spec:
      selector:
          matchLabels:
          app: simple-api
      replicas: 2
      template:
          metadata:
          labels:
              app: simple-api
          spec:
          # 볼륨, pvc에 요청한다.
          volumes:
              - name: sqlite3-pv-storage
              persistentVolumeClaim:
                  claimName: sqlite3-pv-claim
          # 컨테이너 관련 설정
          containers:
              - name: simple-api
              image: a3magic3pocket/simple-api:0.0.7
              ports:
                  - containerPort: 8080
              volumeMounts:
                  - mountPath: "/root/api/sqlite3"
                  name: sqlite3-pv-storage
              env:
                  - name: USE_K8S
                  valueFrom:
                      configMapKeyRef:
                      name: simple-api
                      key: useK8s
                  - name: FRONTEND_URL_LIST
                  valueFrom:
                      configMapKeyRef:
                      name: simple-api
                      key: frontendUrlList
                  - name: GIN_MODE
                  valueFrom:
                      configMapKeyRef:
                      name: simple-api
                      key: ginMode
                  - name: IDENTITY_KEY
                  valueFrom:
                      secretKeyRef:
                      name: simple-api-secret
                      key: identityKey
                  - name: AUTH_SECRET_KEY
                  valueFrom:
                      secretKeyRef:
                      name: simple-api-secret
                      key: authSecretKey
      ---
      # 서비스 설정
      apiVersion: v1
      kind: Service
      metadata:
      name: simple-api
      labels:
          app: simple-api
      spec:
      ports:
          - port: 8080
          targetPort: 8080
      selector:
          app: simple-api
      # type: LoadBalancer
      # type: NodePort
      type: ClusterIP
        
    

Manifest: simple-web.yml

  • 설명
    • deployment
    • simple-locker의 프론트 서버 deployment이다.
  • simple-web.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: simple-web
      spec:
      selector:
          matchLabels:
          app: simple-web
      replicas: 2
      template:
          metadata:
          labels:
              app: simple-web
          spec:
          containers:
              - name: simple-web
              image: a3magic3pocket/simple-web:0.0.19
              ports:
                  - containerPort: 3000
              env:
                  - name: BACKEND_ORIGIN
                  valueFrom:
                      configMapKeyRef:
                      name: simple-web
                      key: backendOrigin
                  - name: FRONTEND_ORIGIN
                  valueFrom:
                      configMapKeyRef:
                      name: simple-web
                      key: frontendOrigin
      ---
      apiVersion: v1
      kind: Service
      metadata:
      name: simple-web
      labels:
          app: simple-web
      spec:
      ports:
          - port: 3000
          targetPort: 3000
      selector:
          app: simple-web
      # type: LoadBalancer
      # type: NodePort
      type: ClusterIP
        
    

Manifest: ingress.yml

  • 설명
  • ingress.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
      name: simple-ingress
      annotations: 
          kubernetes.io/ingress.class: 'nginx' 
          nginx.ingress.kubernetes.io/rewrite-target: / 
          nginx.ingress.kubernetes.io/ssl-passthrough: 'true'
          cert-manager.io/issuer: "letsencrypt-prod"
          # cert-manager.io/issuer: "letsencrypt-staging"
      spec:
      tls:
          - hosts:
              - www.your-frontend-url.com
              - your-frontend-url.com
              - your-backend-url.com
              - your-webhook-url.com
              secretName: simple-tls
      rules:
          - host: "your-backend-url.com"
          http:
              paths:
              - pathType: Prefix
                  path: / 
                  backend:
                  service:
                      name: simple-api
                      port: 
                      number: 8080
          - host: "your-frontend-url.com"
          http:
              paths: 
              - pathType: Prefix
                  path: / 
                  backend:
                  service:
                      name: simple-web 
                      port: 
                      number: 3000
          - host: "www.your-frontend-url.com"
          http:
              paths: 
              - pathType: Prefix
                  path: / 
                  backend:
                  service:
                      name: simple-web 
                      port: 
                      number: 3000
          - host: "your-webhook-url.com"
          http:
              paths:
              - pathType: Prefix
                  path: /
                  backend:
                  service:
                      name: webhook-svc
                      port:
                      number: 9000
    

실험시작

  • minikube 설치, kubectl 설치, minikube 실행
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
      # 우분투 환경 구성
      sudo apt-get update && sudo apt-get upgrade
        
      # minikube 설치
      curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
      sudo install minikube-linux-amd64 /usr/local/bin/minikube
        
      # 도커 설치(기존 도커가 있다면 삭제하고 진행)
      sudo apt-get remove docker docker-engine docker.io containerd runc
        
      ## 필요한 프로그램 설치
      sudo apt-get install \
          ca-certificates \
          curl \
          gnupg \
          lsb-release
        
      ## Add Docker’s official GPG key:
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
        
      ## Use the following command to set up the stable repository
      echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
        
      # 설치
      sudo apt-get update
      sudo apt-get install docker-ce docker-ce-cli containerd.io
        
      # minikube default driver 도커로 변경.
      minikube config set driver docker
        
      # 지금 접속한 유저를 docker group에 포함시킴
      sudo usermod -aG docker $USER && newgrp docker
            
      # kubectl 설치
      curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
      sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
        
      # minikube 시작
      minikube start --memory=1937mb
    
  • 매니페스트 파일 확보
    1
    2
    3
    4
    
      git clone https://github.com/a3magic3pocket/simple-manifest.git
      cd simple-manifest
      # 본인 환경에 맞에 매니페스트 파일을 수정합니다.
      # ex) your-frontend-url.com -> realmyapp.com
    
  • ingress 애드온 설치 및 매니페스트 적용(순서 중요)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
      # 시크릿 및 컨피그 배포
      kubectl apply -f config.yml
      kubectl apply -f secret.yml
        
      # pv 및 pvc 배포
      kubectl apply -f sqlite3-pv.yml
      kubectl apply -f sqlite3-pvc.yml
        
      # webhook url 엔드포인트 서비스 배포
      kubectl apply -f webhook-endpoints.yml
      kubectl apply -f webhook-svc.yml
        
      # 제대로 생성되었나 파드 확인
      # kubectl get pod -o wide
        
      # cerbot 설정
      kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.11.0/cert-manager.yaml
      kubectl apply -f cluster-issuer.yml
        
      # 확인
      kubectl get pods -n cert-manager
        
      # 디플로이먼트 배포
      kubectl apply -f ./simple-api/simple-api.yml
      kubectl apply -f ./simple-web/simple-web.yml
        
      # ingress 관련 addon 활성화
      minikube addons enable ingress
      minikube addons enable ingress-dns
        
      # 인그레스 적용
      kubectl apply -f ingress.yml
        
      # 서비스가 제대로 떴는지 확인 
      curl https://$(minikube ip) -H "Host: [your-domain.com]" -v -k
    
  • rinetd 설치 및 실행
    • 설명
      • minikube라서 그런지 minikube ip가 외부로 노출되지 않는다.
      • rinetd를 사용해서 서버 80,433포트로 오는
        모든 TCP 트래픽을 minikube ip로 포워딩한다.
      • rinetd 사용법
    • 실행
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      
        # rinetd 다운로드 페이지
        # https://github.com/samhocevar/rinetd/releases/tag/v0.73
        # rinetd 다운로드
        wget https://github.com/samhocevar/rinetd/releases/download/v0.73/rinetd-0.73.tar.gz
              
        # 압축 풀기
        tar -xzvf rinetd*.tar.gz
              
        # gcc make를 위해 build essential 받아줌
        sudo apt-get install build-essential
              
        # netstat 설치
        sudo apt-get install net-tools
              
        # 혹시 80 포트가 물려있는지 확인
        netstat -l --numeric-ports -p |grep 80
              
        # minikube ip 확인
        echo $(minikube ip)
              
        # sudo vim /etc/rinetc.conf에 아래 내용 추가
        0.0.0.0 80 [minicube ip] 80
        0.0.0.0 443 [minicube ip] 443
              
        # rinetd 설치
        cd ./rinetd-0.73
        ./bootstrap
        ./configure
        make
        make install
              
        # foreground 실행
        sudo rinetd -c /etc/rinetc.conf -f
      
  • webhook 설정
    • deploy.sh 스크트립트 외에는 docker-swarm-mode와 동일하다.
    • deploy.sh
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        kubectl apply -f config.yml
        kubectl apply -f secret.yml
              
        kubectl apply -f sqlite3-pv.yml
        kubectl apply -f sqlite3-pvc.yml
              
        kubectl apply -f webhook-endpoints.yml
        kubectl apply -f webhook-svc.yml
              
        kubectl apply -f simple-api.yml
        kubectl apply -f simple-web.yml
              
        kubectl apply -f ingress.yml
      
  • 자주 쓰는 명령어
    • Kubectl Cheat Sheet
    • 명령어
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      
        # version check
        minikube version
              
        # minikube 실행
        minikube start
              
              
        # 모든 파드 조회
        kubectl get pods -o wide    
              
        # deployments 확인
        kubectl get deployments
              
        # namespace를 포함하여 모든 deployments 조회
        kubectl get deploy -A
              
        # pods 확인
        kubectl get pods -o wide
              
        # nodes 확인 
        kubectl get nodes
              
        # 서비스 확인
        kubectl get services
              
        # 특정 파드 상세 명세 조회
        kubectl describe pods [pod-name]
              
        # namespace 확인
        kubectl get namespace
              
              
        # yaml 파일 적용
        kubectl apply -f [yaml file path]
              
        # ex) deployment 적용
        kubectl apply -f simple-api.yml
        kubectl apply -f simple-web.yml
              
              
        # replicas 수 조정
        kubectl scale --current-replicas=2 --replicas=3 deployment/[deployment-name]
              
              
        # deployment 삭제
        kubectl delete deploy deploymentname -n namespacename
              
              
        # yaml 로그 확인
        kubectl logs -f [yaml file path]
              
        # ex) deployment 로그 확인
        kubectl logs -f deployment/simple-api
        kubectl logs -f deployment/simple-web
              
              
        # minikube 이미지 삭제
        # Reference by : https://stackoverflow.com/questions/68855754/how-can-i-remove-an-image-from-minikube
        minikube image rm [your-image-id]
      

소감

  • minikube 조차 manager 노드를 실행하려면
    cpu 2core, 2GB memory 사양이 필요하다.
    그만큼 작은 규모의 애플리케이션에는
    쿠버네티스가 과하게 무거운 것 같다.
  • 하지만 애플리케이션 규모가 엄청나게 크고
    각종 배포가 수동으로 중앙에서 제어하기 힘들 정도로 자주 일어나기 시작하면
    쿠버네티스 구조를 일괄 도입하는게 더 관리비용이 저렴해질 것이라 생각한다.
  • simple-locker도 처음엔 minikube로 올렸지만
    AWS 프리티어로 올릴 수가 없어
    현재는 docker-swarm mode로 전환한 상태이다.

참고

This post is licensed under CC BY 4.0 by the author.