Kubernetes

Argo-Rollouts-Pod-Lifecycle


Argo Rollouts 의 쿨다운에 대한 이야기를 하려고한다.

이글은 Argo Roullouts 의 scaleDownDelaySeconds 옵션부터 preStop terminationGracePeriodSeconds 까지의 과정을 다룬다.

Argo Rollouts 는 배포를 도와주는 툴로 블루그린 카나리등과 같은 부분을 도와주는 도구이다. 간략하게 동작을 설명하겠다.

블루그린이 완료된이후 RS는 축소하지 않고 30초간 대기한다. 이 30초간 대기하는 옵션이 scaleDownDelaySeconds다. 혹시나모를 롤백 상황에 대해서 기다리는 옵션인것이다. 이시간이 종료되면 이제 RS는 replicas 를 0으로 수정해서 pod 들은 축소된다.

replicas 가 0으로 수정되면 pod는 일반적인 pod 의 lifecycle 를 거친다. pod 는 여러 차례 말했지만 N+1개의 컨테이너의 집합이고 컨테이너는 프로세스이므로 프로세스의 종료 과정이 그대로 pod 의 lifecycle 를 따르나 쿠버네티스는 이 프로세스를 컨트롤하는 고도화된 툴이므로 다양한 과정을 컨트롤 할수있게 만들어져 있다.

pod 의 종료과정에는 여러 작업을 끼워넣을수 있으며 그 과정중의 하나가 preStop hook 이다. preStop Hook이 설정되면 preStop hook 이 끝날때까지 컨테이너는 종료되지않으며 PreStop 훅이 종료된 시점에 TERM 을 날린다. 그리고terminationGracePeriodSeconds 은 pod 가 종료되는 시점부터 설정된 시간이 끝나면 KILL 시그널을 날린다.

결론적으로 preStop HooK은 terminationGracePeriodSeconds 보다 클수 없다. terminationGracePeriodSeconds 시간내에 행해져야 하고, preStop Hook 보다 커야지만 Graceful 하게 동작할수있다.

CKAD: Certified Kubernetes Application Developer - Review

CKAD를 예약하면서 진짜 공부를 안했다.

정말 안했다.

모든 공부시간을 다 합쳐서도 5시간이 안됐다.

첫번째 시험.. 합격일줄알았는데..떨어졌다.ㅋㅋㅋㅋ
이럴수가ㅋㅋㅋㅋㅋㅋ경악을 금치못했고 공부는 또 안했다. 시험보면서 내가 Docs 에서 원하는 기능이 어디에 있는지 찾는 과정일 뿐 이었기 때문에 그냥 잘 검색하는 방법 yaml 을 좀더 빨리 만들수있게 예제가 있는 위치만 더 찾아봤다.

바로 다시 16일에 시험을 봤고, 합격했다.

고득점일줄 알았는데 나중에 복기해보니 틀린게 좀 있었다.

먼저 Cronjob 은 이제 완전히 옵션을 다 알았다.
SecurityContext는 뭐 그럭저럭..
Docker save 명령어는 생각이 안나서 man docker 해서 봤다.
Readiness 는 httpget이 Docs엔 안나와있는데 나중에 찾아보니 그냥 공통 구조체더라.

12월 말에 이르러서 CKAD를 취득했다.

시험환경 자체는 많이 개선된거로 보인다. 하지만 불편한 건 어쩔 수 없다.

CKAD 재미있었다~

helm-sentry-install-fail

helm install sentry sentry/sentry
coalesce.go:175: warning: skipped value for kafka.config: Not a table.
coalesce.go:175: warning: skipped value for kafka.zookeeper.topologySpreadConstraints: Not a table.
W1023 08:00:35.276931   15594 warnings.go:70] spec.template.spec.containers[0].env[39]: hides previous definition of "KAFKA_ENABLE_KRAFT"
Error: INSTALLATION FAILED: failed post-install: 1 error occurred:
        * job failed: DeadlineExceeded

job failed: DeadlineExceeded 에러가 발생한다.

이 job은 DB가 정상적으로 올라왔는지 확인하는 job이다.

k get job
NAME              COMPLETIONS   DURATION   AGE
sentry-db-check   0/1           5m23s      5m23s

이 Job은 다음을 검증한다.

 name: sentry-db-check
    namespace: sentry
    resourceVersion: "4700657"
    uid: 12533bba-b35b-4b7d-9007-8c625b389a98
  spec:
    activeDeadlineSeconds: 1000
    backoffLimit: 6
    completionMode: NonIndexed
    completions: 1
    parallelism: 1
    selector:
      matchLabels:
        batch.kubernetes.io/controller-uid: 12533bba-b35b-4b7d-9007-8c625b389a98
    suspend: false
    template:
      metadata:
        creationTimestamp: null
        labels:
          app: sentry
          batch.kubernetes.io/controller-uid: 12533bba-b35b-4b7d-9007-8c625b389a98
          batch.kubernetes.io/job-name: sentry-db-check
          controller-uid: 12533bba-b35b-4b7d-9007-8c625b389a98
          job-name: sentry-db-check
          release: sentry
        name: sentry-db-check
      spec:
        containers:
        - command:
          - /bin/sh
          - -c
          - |
            echo "Checking if clickhouse is up"
            CLICKHOUSE_STATUS=0
            while [ $CLICKHOUSE_STATUS -eq 0 ]; do
              CLICKHOUSE_STATUS=1
              CLICKHOUSE_REPLICAS=3
              i=0; while [ $i -lt $CLICKHOUSE_REPLICAS ]; do
                CLICKHOUSE_HOST=sentry-clickhouse-$i.sentry-clickhouse-headless
                if ! nc -z "$CLICKHOUSE_HOST" 9000; then
                  CLICKHOUSE_STATUS=0
                  echo "$CLICKHOUSE_HOST is not available yet"
                fi
                i=$((i+1))
              done
              if [ "$CLICKHOUSE_STATUS" -eq 0 ]; then
                echo "Clickhouse not ready. Sleeping for 10s before trying again"
                sleep 10;
              fi
            done
            echo "Clickhouse is up"

            echo "Checking if kafka is up"
            KAFKA_STATUS=0
            while [ $KAFKA_STATUS -eq 0 ]; do
              KAFKA_STATUS=1
              KAFKA_REPLICAS=3
              i=0; while [ $i -lt $KAFKA_REPLICAS ]; do
                KAFKA_HOST=sentry-kafka-$i.sentry-kafka-headless
                if ! nc -z "$KAFKA_HOST" 9092; then
                  KAFKA_STATUS=0
                  echo "$KAFKA_HOST is not available yet"
                fi
                i=$((i+1))
              done
              if [ "$KAFKA_STATUS" -eq 0 ]; then
                echo "Kafka not ready. Sleeping for 10s before trying again"
                sleep 10;
              fi
            done
            echo "Kafka is up"
          image: subfuzion/netcat:latest
          imagePullPolicy: IfNotPresent
          name: db-check
          resources:
            limits:
              memory: 64Mi
            requests:
              cpu: 100m
              memory: 64Mi
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
        dnsPolicy: ClusterFirst
        restartPolicy: Never
        schedulerName: default-scheduler
        securityContext: {}
        terminationGracePeriodSeconds: 30

Clickhouse / Kafka 가 실행되어야 job은 정상화 가능하다. 시간이 오래걸리는 작업이므로, hook 의 시간을 늘려주면 job은 더 긴시간 대기한다 helm 의 values.yaml 에서 activeDeadlineSeconds를 늘려주면 된다.

hooks:
  enabled: true
  removeOnSuccess: true
  activeDeadlineSeconds: 1000

이 시간을 늘려도 문제가 생긴다면 보통 kafka의 pv가 생성되지 않는경우다.

CSI 컨트롤러를 확인해 보는게 좋다.

EKS-NodeLess-08-AWS-Karpenter-topologySpreadConstraints

topologySpreadConstraints 을 사용한 Karpenter 테스트를 진행하겠다.

topologySpreadConstraints 테스트는 Topology Aware Hint 를 예비한 테스트다.

https://aws.amazon.com/ko/blogs/tech/amazon-eks-reduce-cross-az-traffic-costs-with-topology-aware-hints/

참고할 부분이 있다면 이 글을 참고하길 바란다.

간략하게 설명하자면 Kubernetes 에서 Cross Zone Traffic 의 문제로 비용이 막대하게 발생할수 있다. 또한 Cross-AZ로 인하여 약간의 레이턴시가 발생할수도 있기때문에 Topology Aware Hint는 여러 문제점들을 줄여주는 역할을 한다.

조건은 몇가지가 있는데, Service 로 연결되 AZ가 수평적으로 동일하게 노드가 배포되어있고 서비스에

apiVersion: v1
kind: Service
metadata:
  name: service
  annotations:
    service.kubernetes.io/topology-aware-hints: auto

다음과 같은 annotations 붙어있어야 한다.

그럼먼저 우리는 Provisioner가 자동으로 노드를 Deprovisioning 하도록 설정하자.

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  consolidation:
    enabled: true
  requirements:
    - key: karpenter.k8s.aws/instance-category
      operator: In
      values: [ t, m, c ]
  providerRef:
    name: default
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector:
    karpenter.sh/discovery: "${CLUSTER_NAME}"
  securityGroupSelector:
    karpenter.sh/discovery: "${CLUSTER_NAME}"

consolidation enabled 옵션은 Pod 의 리소스 요구조건에 따라서 Karpenter 가 알아서 노드를 스케줄링한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: host-spread
spec:
  replicas: 20
  selector:
    matchLabels:
      app: host-spread
  template:
    metadata:
      labels:
        app: host-spread
    spec:
      containers:
      - image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
        name: host-spread
        resources:
          requests:
            cpu: "1"
            memory: 256M
      topologySpreadConstraints:
      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 2
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 5
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule

topologyKey: kubernetes.io/zone maxSkew: 2는 하나의 호스트 즉 노드간 파드의 차이는 maxSkew: 2 를 초과할수 없다. topologyKey: topology.kubernetes.io/zone maxSkew: 5 zone 간 pod의 갯수는 maxSkew: 5 초과 할수 없으므로 하나의 zone에서만 pod가 스케줄링된다면 20개의 replicas 를 요청한다 해도 5개의 pod 만 스케줄링 된다.

AZ별로 제공하는 유형의 인스턴스 페일리가 달라서 특정 유형만 사용하려고 하면 프로비저닝 조건에 걸려서 스케줄링이 쉽지 않다.

karpenter 의 강력함은 테스트중에 확인할수 있는데, 42s 만에 Pod 가 Running 된다. 42초안에 Node도 프로비저닝 된다는 말이다.

2023-05-20T11:03:34.806Z	ERROR	controller.provisioner	Could not schedule pod, incompatible with provisioner "default", no instance type satisfied resources {"cpu":"1","memory":"256M","pods":"1"} and requirements karpenter.k8s.aws/instance-category In [c m t], kubernetes.io/os In [linux], kubernetes.io/arch In [amd64], karpenter.sh/provisioner-name In [default], karpenter.sh/capacity-type In [on-demand], topology.kubernetes.io/zone In [ap-northeast-2a]	{"commit": "d7e22b1-dirty", "pod": "default/host-spread-fbbf7c9d9-x4lfd"}

topologySpreadConstraints 옵션을 테스트하면서 느꼈는데, 여러 요인들로 잘 스케줄링하지 못한다.

두가지 조건에 의해서 pod는 모두다 스케줄링 되지 못하는데, 노드를 스케줄링하지 못해서 걸리기도 한다. 조건은 확실히 걸리긴한다.

k get node --show-labels | grep -v fargate | awk -F"topology.kubernetes.io/" '{print $3}' | sort
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2c

다음과같이 노드가 a 3대 b 3대 c 1대 스케줄링 되면 모두 14개의 pod가 스케줄링된다. C zone때문에 두번째 조건에 걸리기 때문이다. 다양한 조건을 사용하면 이와 같이 균등하게 zone 에 스케줄링 하긴 어려운점이 있다. 적당히 조건을 걸어주면 잘 작동한다.

      - labelSelector:
          matchLabels:
            app: host-spread
        maxSkew: 2
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule

이 조건을 삭제하면 topology.kubernetes.io/zone maxSkew 5 만 남겨서 프로비저닝 해보면 비대칭으로 az 에 node가 프로비저닝 되지만 조건에만 맞는다면 pod를 모두 생성한다.

k get pod
NAME                          READY   STATUS    RESTARTS   AGE
host-spread-dd5f6c569-49ps7   1/1     Running   0          115s
host-spread-dd5f6c569-8772p   1/1     Running   0          115s
host-spread-dd5f6c569-9q2hn   1/1     Running   0          115s
host-spread-dd5f6c569-b68k2   1/1     Running   0          115s
host-spread-dd5f6c569-bfhv5   1/1     Running   0          115s
host-spread-dd5f6c569-bqqz2   1/1     Running   0          116s
host-spread-dd5f6c569-bsp8m   1/1     Running   0          115s
host-spread-dd5f6c569-dh8wx   1/1     Running   0          115s
host-spread-dd5f6c569-ffjdg   1/1     Running   0          115s
host-spread-dd5f6c569-jghmr   1/1     Running   0          115s
host-spread-dd5f6c569-jhbxg   1/1     Running   0          116s
host-spread-dd5f6c569-kf69q   1/1     Running   0          115s
host-spread-dd5f6c569-ksktv   1/1     Running   0          115s
host-spread-dd5f6c569-lbqmv   1/1     Running   0          115s
host-spread-dd5f6c569-mbf2g   1/1     Running   0          116s
host-spread-dd5f6c569-pd92p   1/1     Running   0          115s
host-spread-dd5f6c569-pgphc   1/1     Running   0          115s
host-spread-dd5f6c569-ph59g   1/1     Running   0          115s
host-spread-dd5f6c569-sdp7d   1/1     Running   0          115s
host-spread-dd5f6c569-tf8v9   1/1     Running   0          115s
(user-linuxer@myeks:default) [root@myeks-bastion-EC2 EKS]# k get node --show-labels | grep -v fargate | awk -F"topology.kubernetes.io/" '{print $3}' | sort
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2a
zone=ap-northeast-2b
zone=ap-northeast-2b
zone=ap-northeast-2c

AZ 별로 균등하게 node를 프로비저닝 해야하는 방법이 필요하다.

https://github.com/aws/karpenter/issues/2572

일단 테스트한 결과와 git issues 를 보면 karpenter 가 topologySpreadConstraints 에 적절히 대응되지 않는것을 느낄수 있었다. 따라서 minDomains 옵션으로 3개의 zone을 지정도 해보았으나 썩 좋은 결과는 없었다.

따라서 다이나믹하게 Node를 프로비저닝하면서 사용할수는 없을것같고, 미리 Node를 프로비저닝 하는 구성에선 될법한데, 그건 Karpenter 의 패턴은 아니라고 느꼈다.