[쿠버네티스 심화] 투두 리스트 앱 Yaml 파일 분석 (from PVC to Headless-Serivce)

Jisu·2023년 12월 6일

Kubernetes

목록 보기
8/12
post-thumbnail

배경

지난 시간에는 투두 리스트 앱을 배포하는데 사용된 쿠버네티스 워크로드 리소스들 중 가장 길었던 StatefulSets에 대해서 분석했다.

독립된 상태를 유지하는 리소스이기 때문에 mysql같은 데이터베이스 배포에 사용되며 크게 아래 사항들이 명세되었다.

1. 앱 실행에 필요한 베이스 이미지
2. 클러스터 내부 파드와 통신하기 위한 포트와 DNS 및 통신 프로토콜(TCP)
3. db 비밀번호와 같은 환경 변수들
4. 영구저장을 위한 volumeClaimTemplates
5. Pod 상태 관리 설정을 위한 Probe

이번에는 StatefulSets 이외 나머지 yaml 파일에 대해서 분석해보자.


PVC

k9s를 사용하여 PVC를 검색해보자.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  annotations:
    pv.kubernetes.io/bind-completed: "yes"
    pv.kubernetes.io/bound-by-controller: "yes"
    volume.beta.kubernetes.io/storage-provisioner: k8s.io/minikube-hostpath
    volume.kubernetes.io/storage-provisioner: k8s.io/minikube-hostpath
  creationTimestamp: "2023-12-05T15:25:15Z"
  finalizers:
  - kubernetes.io/pvc-protection
  labels:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: mysql-helm
    app.kubernetes.io/name: mysql
  name: data-mysql-helm-0
  namespace: helm-test
  resourceVersion: "627801"
  uid: 8b3be04f-eee7-4c6f-bcd0-7ee92efb0a40
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: standard
  volumeMode: Filesystem
  volumeName: pvc-8b3be04f-eee7-4c6f-bcd0-7ee92efb0a40
status:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 8Gi
  phase: Bound
  • pvc-8b3be04f-eee7-4c6f-bcd0-7ee92efb0a40 라는 PV로 claim한다고 명시 (이와 동일한 uid를 가지는 PV를 생성)
  • 저장공간 8Gi, ReadWriteOnce로 요청

PV

파드별 PVC가 만들어졌으니 그를 바탕으로 PV가 만들어진다. PV 명세파일을 분석해보자. 핵심 내용은 아래와 같다.

  • 호스트의 path: /tmp/hostpath-provisioner/helm-test/data-mysql-helm-0 에 볼륨 마운트
  • Claim한 PVC의 이름과 네임스페이스 명시
  • uid: PVC에서 요청한 것과 동일하게 생성
apiVersion: v1
kind: PersistentVolume
metadata:
  annotations:
    hostPathProvisionerIdentity: b3b2965f-b900-4ffd-956f-34e3191e33b5
    pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath
  creationTimestamp: "2023-12-05T15:25:15Z"
  finalizers:
  - kubernetes.io/pv-protection
  name: pvc-8b3be04f-eee7-4c6f-bcd0-7ee92efb0a40
  resourceVersion: "627793"
  uid: 22d969c2-124d-439c-84b0-f062f50924f6
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 8Gi
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: data-mysql-helm-0
    namespace: helm-test
    resourceVersion: "627785"
    uid: 8b3be04f-eee7-4c6f-bcd0-7ee92efb0a40
  hostPath:
    path: /tmp/hostpath-provisioner/helm-test/data-mysql-helm-0
    type: ""
  persistentVolumeReclaimPolicy: Delete
  storageClassName: standard
  volumeMode: Filesystem
status:
  phase: Bound

Headless serivce

Headless Service는 외부 IP 주소가 할당되지않는 서비스이다.

Headless Service는 StatefulSet의 각 파드에 대한 개별적인 DNS 레코드를 제공한다. 이는 파드의 고유한 식별자에 따라 DNS 이름이 생성되어 클라이언트가 직접적으로 각 파드에 접근할 수 있게 해준다.

minikube로 앱을 배포할 때 외부와 통신을 가능하게 하기 위해 외부에 열어둘 포트와 트래픽을 라우팅할 컨테이너의 target port를 명시했다. 그를 통해 외부 트래픽을 랜덤한 파드에 배분하여 라우팅시킬 수 있었다. (로드 밸런싱)

로드밸런싱이 필요없고 일반적인 경우에 다른 파드에 db 연결을 제공하는 용도로 사용되어서 외부 IP가 필요없다. 그래서 Statefulsets은 headless service와 함께 배포된다.

apiVersion: v1
kind: Service
metadata:
  annotations:
    meta.helm.sh/release-name: mysql-helm
    meta.helm.sh/release-namespace: helm-test
  creationTimestamp: "2023-12-05T15:25:14Z"
  labels:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: mysql-helm
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.0.35
    helm.sh/chart: mysql-9.14.4
  name: mysql-helm-headless
  namespace: helm-test
  resourceVersion: "627775"
  uid: 678308c3-60d4-4019-b512-b7840157c270
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
  publishNotReadyAddresses: true
  selector:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: mysql-helm
    app.kubernetes.io/name: mysql
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
  • headless이기 때문에 clusterIP는 none
  • 3306포트의 트래픽 수신하여 네임스페이스 내에 mysql 이라는 이름의 DNS로 라우팅

Deployment / Service

todo-app의 deployment 명세

  • rolling update 전략 명시: 최소 75% 파드를 가용가능하게 하고 추가 생성시 25%까지만 허용
  • 환경변수로 mysql root 사용자, 비밀번호, 연결 database 설정
    (Helm chart 기본 설정인 my_database로 설정)
  • 컨테이너 개방 포트는 3000 (service에서 라우팅할 타겟포트)
apiVersion: v1
kind: Service
metadata:
  annotations:
    meta.helm.sh/release-name: mysql-helm
    meta.helm.sh/release-namespace: helm-test
  creationTimestamp: "2023-12-05T15:25:14Z"
  labels:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: mysql-helm
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.0.35
    helm.sh/chart: mysql-9.14.4
  name: mysql-helm-headless
  namespace: helm-test
  resourceVersion: "627775"
  uid: 678308c3-60d4-4019-b512-b7840157c270
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
  publishNotReadyAddresses: true
  selector:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: mysql-helm
    app.kubernetes.io/name: mysql
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
jisujang@Jisuui-MacBookPro ~ % kubectl get deployment todo-app -o yaml            
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"todo-app","namespace":"helm-test"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"todo-app"}},"template":{"metadata":{"labels":{"app":"todo-app"}},"spec":{"containers":[{"args":["yarn install \u0026\u0026 yarn run dev"],"command":["sh","-c"],"env":[{"name":"MYSQL_HOST","value":"mysql-helm"},{"name":"MYSQL_USER","value":"root"},{"name":"MYSQL_PASSWORD","value":"OnOJGdH7Lv"},{"name":"MYSQL_DB","value":"my_database"}],"image":"jjrk/docker_tutorial","name":"node-app","ports":[{"containerPort":3000}],"workingDir":"/app"}]}}}}
  creationTimestamp: "2023-12-05T15:39:04Z"
  generation: 1
  name: todo-app
  namespace: helm-test
  resourceVersion: "629200"
  uid: 628d39d3-56fc-4c6a-9567-05e9c81aaa50
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: todo-app
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: todo-app
    spec:
      containers:
      - args:
        - yarn install && yarn run dev
        command:
        - sh
        - -c
        env:
        - name: MYSQL_HOST
          value: mysql-helm
        - name: MYSQL_USER
          value: root
        - name: MYSQL_PASSWORD
          value: OnOJGdH7Lv
        - name: MYSQL_DB
          value: my_database
        image: jjrk/docker_tutorial
        imagePullPolicy: Always
        name: node-app
        ports:
        - containerPort: 3000
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        workingDir: /app
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 3
  conditions:
  - lastTransitionTime: "2023-12-05T15:39:04Z"
    lastUpdateTime: "2023-12-05T15:39:26Z"
    message: ReplicaSet "todo-app-85fff4bf6" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2023-12-06T01:17:42Z"
    lastUpdateTime: "2023-12-06T01:17:42Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 1
  readyReplicas: 3
  replicas: 3
  updatedReplicas: 3

Service

서비스는 파드를 외부에 노출시키는 객체이다.(파드는 일반적으로 deployment를 통해서 배포되지만 개별적으로 따로 명세해서 배포할 수 있기도 하다)따라서 명세 정보에서 핵심 사항들은 아래와 같다.

  • 외부에 개방되는 포트는 80, 80번 포트로 수신되는 트래픽을 컨테이너의 3000포트로 라우팅
  • 트래픽 수신 후 todo-app 이름의 컨테이너를 찾아 라우팅
  • 로드 밸런싱 설정
  • node Port는 30972로 할당

** nodePort는 클러스터 외부에서 직접 접근할 때 사용되는 포트이며, 클라이언트는 로드 밸런서의 IP 주소와 nodePort를 통해 Service로 요청을 보낼 수 있다. 이후 요청은 로드 밸런서를 통해 적절한 파드로 분산되어 처리된다. (외부 로드밸런서를 사용할 경우에 사용)

apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"todo-app","namespace":"helm-test"},"spec":{"ports":[{"port":80,"protocol":"TCP","targetPort":3000}],"selector":{"app":"todo-app"},"type":"LoadBalancer"}}
  creationTimestamp: "2023-12-05T15:39:04Z"
  name: todo-app
  namespace: helm-test
  resourceVersion: "628522"
  uid: bd2f46ed-b1ce-40f6-954e-51723b9b8b8b
spec:
  allocateLoadBalancerNodePorts: true
  clusterIP: 10.101.64.212
  clusterIPs:
  - 10.101.64.212
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - nodePort: 30972
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: todo-app
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer: {}
profile
기술 공유를 즐기는 DevOps Engineer 장지수입니다.

0개의 댓글