3주차 - Pod 그리고 RDS 연결

월간-CS | 쿠버네티스 비기너 클래스
이민석's avatar
Aug 11, 2024
3주차 - Pod 그리고 RDS 연결

지금까지

아래 과정을 수행하였습니다.

  1. 1주차 - Docker & EKS 기초

  2. 2주차 - 다양한 유형의 앱 배포하기

이번 주차에는

배포된 Pod에 환경변수를 안전하게 주입하기 위한 Secrets와 컨테이너 실행 이후 일정한 명령어를 실행하도록하는 Init Container 그리고 Pods의 정상 실행 여부를 확인하는 등의 Liveness & Readiness Probes 마지막으로 Pod가 사용가능한 최소 & 최대 CPU/MEM 용량인 Requests & Limits 등이 있습니다.

또한 각각의 K8s Object에 대한 격리 수준을 설정할 수 있는 namepsace가 있습니다.

Secrets

Kubernetes Secrets let you store and manage sensitive information, such as passwords, OAuth tokens, and ssh keys.

Storing confidential information in a Secret in a Secret is safer and more flexible than putting it directly in a Pod definition or in a container image.

Secrets Manifest

기존의 Secrets Manifest에서 MySQL Deployment의 .spec.template.spec.containers[*].env[*]{{ name == "MYSQL_ROOT_PASSWORD" }}란에 아래와 같이 환경변수를 하드 코딩 했습니다.

            - name: MYSQL_ROOT_PASSWORD
              value: dbpassword11

하드 코딩한 환경변수를 아래와 같이 Secrets에 저장할 수 있습니다.

apiVersion: v1
kind: Secret
metadata:
  name: mysql-db-password

type: Opqeue
data:
  db-password: ZGJwYXNzd29yZDEx

이후 하드 코딩한 부분을 다음과 같이 변경할 수 있습니다.

            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                   name: mysql-db-password
                   key: db-password

추가적으로 해당 MySQL Application을 참조하고 있는 usermgmt-microservice Application도 변경해야 합니다.

           - name: DB_PASSOWRD
             value: "dbpassword1!"

변경 후에는 다음과 같습니다.

           - name: DB_PASSOWRD
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                   name: mysql-db-password
                   key: db-password

수정을 완료했으면 다음 명령어를 이용하여 코드를 반영합시다.

kubectl apply -f <FOLDER NAME>/
kubectl apply -f kube-manifest/

[Tips]
특정 Manifest을 지정하지 않고 폴더를 지정하면,
해당 폴더 안에 있는 모든 K8s Manifest가 생성됩니다.

아래 명령어로 Pods 현황과 로그를 조회할 수 있습니다.

kubectl get pods
kubectl logs -f <POD_NAME>

최종적으로 실습이 종료한 K8s Objects들을 제거합시다.

kubectl apply -f <FOLDER NAME>/

kubectl delete -f kube-manifest/

Init Containers

  • Init Containers run before App Containers

  • Init Containers can contain utilities or setup scrtips not present in an app image.

  • We can have an run multiple Init Conatienrs before App Container.

  • Init Conatiners are exactly like regular containers, except:

    • Init Conatiner always run to completion.

    • Each Init Container must complete successfully before the next one starts.

  • If a Pod’s Init Container fails, K8s repeatdly restarts the Pod until the Init Container succeeds.

  • However, if the Pod has a restartPolicy or Never, K8s does not restart the pod.

  • Update initContainers section under Pod Template Spec which is .spec.template.spec in a Deployment

Deployment Manifest for Init Container

이번에도 Deployment를 이용해서 Pods를 만들 것입니다.
따라서 .spec.template.spec에 InitConatienr를 생성하도록 선언할 것입니다.

template:
  metadata:
    labels:
      app: usermgmt-restapp
  spec:
    initContainers:
     - name: init-db
       image: busybox:1.31
       commands: [
         'sh',
         '-c',
         'echo -e "Checking for the availability of MySQL Server deployment"; while ! nc -z mysql 3306; do sleep 1; printf "-"; done; echo -e "  >> MySQL DB Server has started";'
       ]
    containers:

Liveness/Readiness/Startup Probe”

  1. Liveness Probe

    1. K8s uses liveness probes to know when to restart a container.

    2. Liveness probes could catch a deadlock, where an application is running, but unable to make progress and restarting container helps in such state.

  2. Readiness Probe

    1. Kubelet uses readiness probes to know when a container is ready to accept traffic.

    2. When a Pod is not ready, it’s removed from Service Load Balancers based on this readiness probe signal.

  3. Startup Probe

    1. Kubelets uses startup probes to know when a container application has started.

    2. Firstly this proble disables liness & readiness checks unitl it succeeds ensuring those pods don’t interface with app startup.

    3. This can be used to adopt liveness checks on slow starting containers, avoiding them getting killed by the kubelet before they ar eup and running.

Deployment Manifest for **Probes

현재 회사에서 일부 Readiness Probes설정을 사용하고 있습니다. 하지만 일부 SpringBoot/Golang Application에서 서버 실행 직후 에러가 났지만 이를 감지하지 못하는 문제가 있습니다.

이 부분에 대해서 Startup Probes를 도입하는 등으로 서비스 가용성을 보장하는 다양한 활용 사례가 있을 것으로 보였습니다.

실제로 사용가능한 경우는 ShellScript, HTTP, TCP Communication으로 3가지가 존재합니다.

  1. Check using ShellScript : /bin/sh -c nc -z localhost 8095

  2. Check using HTTP GET Request : httpget path:/health-status

  3. Check using TCP : tcpSocketProts 8095

이번에도 Deployment로 Pod를 실행할 겁니다.
따라서 .spec.template.spec.containers[*].livenessProbes 등으로 설정을 진행합니다. → [Ref]

          livenessProbe:
            exec:
              command:
                - /bin/sh
                - -c
                - nc -z localhost 8095
            initialDelaySeconds: 60
            periodSeconds: 10

HTTP Get 요청을 보내는 것으로 설정이 가능하기도 합니다.

          readinessProbe:
            httpGet:
              path: /usermgmt/health-status
              port: 8095
            initialDelaySeconds: 60
            periodSeconds: 10

강의에는 나오지 않았지만 동일한 조건에 대해서 startupProbe도 정의 가능합니다. [Ref]

          startupProbe:
            httpGet:
              path: /usermgmt/health-status
              port: 8095
            initialDelaySeconds: 60
            periodSeconds: 10

Request & Limits

  • We can specify how much each container a pod needs the resources like CPU & Memory.

  • When we provide this information in our pod, the scheduler uses this infromation to decided which node to place the Pod on.

  • When you specify a resource limit for a Container, the kubelet enforces those limits so that the running container is not allowed to use more or that resource than the limti you set.

  • The kubelet also reserves at least the request amount of that system resource specifically for that container to use

Deployment Manifest for Reqeust & Limits

다음 예시를 통해서 Request 및 Limits 설정을 볼 수 있습니다. → [Ref]
특이한 점은 Megabyte가 아니라 Memibyte라는 단위를 사용하며 CPU에서는 milliCPU라는 단위를 사용하여 1,000 milliCPU는 1 vCPU 만큼의 단위를 의미합니다.

          resources:
            requests:
              memory: "128Mi" # 128 MI == 135 MB
              cpu: "500m" # `m` means milliCPU
            limits:
              memory: "500Mi"
              cpu: "1000m"  # 1000m is equal to 1 VCPU core

일반적으로 서버 1개가 가동되기 위한 최소 vCPU가 존재하며,
1개의 vCPU로 할 수 있는 작업의 총량 및 한계가 어느정도 존재합니다.
따라서 지속적인 모니터링을 통해서 Request & Limit 최적점을 탐색해야 합니다.

Namespaces

  • Namespaces are also called Virtual Cluster in our physical k8s cluster.

  • We use this in environments where we have many users spread across multiple teams or projects.

  • Cluster with tens of users ideally don’t need to use namespaces.

  • Benefits

    • Creates isolation boundary from other k8s objects.

    • We can limit the resources like CPU, Memory on per namespace basis (Resources Quota)

K8s에서는 모든 대상을 Objects를 통해서 배포합니다.
따라서 Namespace도 다음과 같은 kind: Namspace로 분류됩니다.

Namespace Manifets

apiVersion: v1
kind: namespace
metadata:
  name: dev3

Limit Range

하나의 네임스페이스에 5개의 Deployment가 있으며, 모두 동일한 Request & Limit을 가진 경우, LimitRange를 통해서 이를 깔끔하게 관리할 수 있습니다.

Limit Range Manifest

다음 예시를 통해서 Request & Limit을 Namespace + LimitRange를 이용해서 할당할 수 있습니다.

apiVersion: v1
kind: Namespace
metadata:
  name: dev3
---
apiVersion: v1
kind: LimitRange
metadata:
  name: default-cpu-mem-limit-rnage
  namespace: dev3

spec:
  limits;
    - default:           # default for limit
        memory: "512Mi"
        cpu: "500m"
      defaultRequest:    # defulatRequest for request
        memory: "256Mi"
        cpu: "300m"
      type: Container

추가적으로 기존에 작성해둔 K8s Manifest 들 모두 .metadata.namespace 항목을 할당하도록 합시다.

ResourceQuota

ResourceQuota는 K8s에서 Namespace 별로 리소스 사용량을 제한할 수 있습니다. CPU, MEM, PVC 등의 리소스 최댓값을 제어할 수 있습니다.

이를 통해서 자원 분배를 공평하게 하고 오버프로비저닝으로 방지합니다.

이 부분이 조금 신기했는데,
AWS ServiceQuota와 같은 기능을 가진 친구 처럼 보여졌습니다.

Resource Quota Manifest

apiVersion: v1
kind: ResourceQuota
metadata:
  name: ns-resource-quota
  namespace: dev3
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi

    limits.cpu: 2
    limits.memory: 2Gi

    pods: 5
    configmaps: 5
    persistentvolumeclaims: 5
    secrets: 5
    services: 5

MySQL, EKS(Yourself) and RDS(+ExternalDNS)

일반적으로 MySQL을 사용하는 2가지 방법이 존재합니다.
많은 이유에 따라 일반적으로 RDS를 사용하는 것이 구조적, 비용적으로 효율적인 순간이 있을 수 있습니다.

MySQL with yourself

RDS

Drawbacks of EBS CIS for MySQL DB

High Availability

Complex setup to archieve HA

Backup & Recovery

Complex Multi-AZ support for EBS

Read Replicas

Complex Master-Master MySQL Setup

Metrics & Monitoring

Complex Master-Slave MySQL Setup

Automatic Upgrades

No Automatic Backup & Recoveryu

Multi-AZ Support

No Auto-Upgrade MySQL

RDS를 배포하고 이를 연동하는 방법에 대해서 가이드합니다.

MySQL ExternalDNS Manifest

생성한 RDS Instance의 Endpoint를 ExternalName Service로 생성합니다.

apiVersion: v1
kind: Service
metadata:
  name: rds-hostname
spec:
  type:  ExternalName
  externalName: <ID>.<UNIQUE_KEY>.<REGION>.rds.amazonaws.com

이후 Deployment의 .spec.template.spec.containers[*].env[{{ .name == ‘DB_HOST_NAME’ }}.value에 rds-hostname을 참조할 수 있습니다.

          env:
            - name: DB_HOSTNAME
              value: "rds-hostname"

추가 공부 - ExternalName을 사용해야 하는 이유

2주차 공부를 하면서 Secrets & ConfigMap 만으로도 충분히 Application 관리가 가능할 것 같다고 생각했습니다. 그런데 ExternalName을 사용해야 할까요?

지금까지 배운 내용으로는 usermgmt-microservcies APP에 3가지 방법으로 변수를 전달하고 있습니다.

  1. 하드 코딩

  2. Secrets

  3. ConfigMap (2주차 추가 공부 중)

하지만 .spec.template.spec.containers[*].env에 있는 곳에 아래와 같이 작명된 KEY가 전달된다면 어떨까요?

          env:
            - name: DB_HOSTNAME
              value: "rds-hostname-external-name"

이 경우 다음과 같은 5가지 장점이 증가합니다.

  1. DNS 레코드 관리 가능

  2. 서비스 명세의 단순화

  3. 네트워크 정책 적용 가능

  4. 호환성

  5. 외부 서비스의 IP 주소 변경에 대한 대응

소감

2주차 추가 공부 - Tips에서 공부한 내용이 많이 포함되어 있어서 편하고 재밌게 들었습니다.

  1. 도입하면 좋을 부분

    1. Liveness/Readiness/StartupProbes

    2. ExternalDNS

  2. Metric Server와 동시에 도입하면 좋을 부분

    1. Request & Limit

    2. LimitRange

    3. ResourceQuotas

Request & Limit, LimitRange, ResourceQuotas는 전체적으로 통제의 성질이 강한 K8s Objects들이라고 느껴졌습니다.

Share article

Unchaptered