개요

쿠버네티스 클러스터가 커져감에 따라 새로 생겨나는 Worker Node에 Docker Image가 Pull되어 있지 않은 상태에서 Pod을 띄울때 상당히 오랜시간이 걸리는 것을 보고 개선 방안을 생각해보았다. sandbox와 prod 클러스터에 노드가 200대 씩 되고나니 자동화나 개선의 필요성이 느껴졌기 때문이다.

개선요약

  • k8s에는 kind중에 daemonset 이 존재하는데 동시에 모든 Worker Node에 명령을 내릴수 있음
  • 기본적으로 daemonset은 무한반복하는데 이를 한번만 실행하게 yaml을 만든 뒤 shell script로 daemonset을 실행 후 종료 시킴

daemonset.yaml 정의

initContainer로 docker pull을 수행한 뒤 실제로는 puase 컨테이너가 동작하는 Trick으로 daemonset이 단한번만 동작하게 만든다. 예제에서는 hello-world docker image를 pull받는다.

apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
  name: kwangsiklee-prepull
spec:
  selector:
    matchLabels:
      name: kwangsiklee-prepull
  template:
    metadata:
      labels:
        name: kwangsiklee-prepull
    spec:
      initContainers:
      - name: kwangsiklee-prepull
        image: docker
        command: ["docker", "pull", "hello-world"]
        volumeMounts:
        - name: docker
          mountPath: /var/run
      volumes:
      - name: docker
        hostPath:
          path: /var/run
      containers:
      - name: pause
        image: gcr.io/google_containers/pause

run-once-daemoneset.sh

스크립트를 통해 daemonset을 생성하면 initContainers 단계에서 docker 이미지를 pull 받은 뒤 연달아 pause 컨테이너를 실행한 상태에서 Pod이 Running 상태로 바뀐다. 이후 모든 Pod이 다 running으로 바뀌길 기다린 후 daemonset을 삭제한 뒤 종료한다.

#!/bin/bash

set -euo pipefail

function main() {
  kubectl apply -f daemonset.yaml
  wait_for_pods kwangsiklee-prepull
  kubectl delete -f daemonset.yaml
}

function wait_for_pods() {
  echo -n "waiting for $1 pods to run"
  sleep 5
  PODS=$(kubectl get pods | grep $1 | awk '{print $1}')

  for POD in ${PODS}; do
    while [[ $(kubectl get pod ${POD} -o go-template --template "{{.status.phase}}") != "Running" ]]; do
      sleep 1
      echo -n "."
    done
  done

  echo
}

main

검증하기

쉘을 실행해보면 아래와 같이 정상 실행됨을 알 수 있다.

daemonset.yaml만 따로 실행해보면 아래와 같이 각 Pod이 모두 Running되는것을 알 수 있다.

daemonset도 조회해보면 Worker Node 200개에 정상적으로 적용된것을 확인 할 수 있다.

응용하기 #1

필자의 조직에서는 젠킨스를 통해 Docker Build & Push를 관리한다. 이제 젠킨스를 통해 Docker Image Build & Push Job 이후로 연달아 k8s Worker Node에 daemonset이 실행되게 downstream job을 걸어두면 Docker Build 후 자동으로 전체 Worker Node에 Docker Pull을 받아두게 된다.

예를들어 아래와 같이 Docker Build Job이 있으면 이 후 prepull하는 job을 downstream 걸어둔다.

앞으로 docker build 후 연달아 prepull job이 실행되어 전체 worker node에 docker image가 pull된다.

응용하기 #2

조금 더 응용하면 docker image 및 deploy phase(개발 phase인 sandbox 인지 상용 phase인 prod인지)등을 고려하여 동적 스크립트화 할 수 있다.

apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
  name: {{IMAGE_NAME}}-prepull-{{PHASE}}
spec:
  selector:
    matchLabels:
      name: {{IMAGE_NAME}}-prepull-{{PHASE}}
  template:
    metadata:
      labels:
        name: {{IMAGE_NAME}}-prepull-{{PHASE}}
    spec:
      initContainers:
      - name: {{IMAGE_NAME}}-prepull-{{PHASE}}
        image: docker
        command: ["docker", "pull", "{{IMAGE_NAME}}:{{TAG}}"]
        volumeMounts:
        - name: docker
          mountPath: /var/run
      volumes:
      - name: docker
        hostPath:
          path: /var/run
      containers:
      - name: pause
        image: gcr.io/google_containers/pause
#!/bin/bash

if [ "$#" -eq 0 ]; then
    echo "Usage: $0 image_name tag (prod|sandbox)" >&2
    exit 1
fi


if [ $3 == 'prod' ]; then
    prod 클러스터로 change
else
    sandbox 클러스터로 change
fi

set -euo pipefail

function wait_for_pods() {
  echo "waiting for $1 pods to run"

  sleep 15

  while [[ $(kubectl get daemonset | sed -n 2p | awk '{print $2}') -gt $(kubectl get daemonset | sed -n 2p | awk '{print $4}') ]]; do
    sleep 3
    echo "progress: $(kubectl get daemonset | sed -n 2p | awk '{print $4}')/$(kubectl get daemonset | sed -n 2p | awk '{print $2}')"
  done

  echo "completed"
}


cat daemonset.yaml | sed "s/{{PHASE}}/$3/g" | sed "s/{{IMAGE_NAME}}/$1/g" | sed "s/{{TAG}}/$2/g" |  kubectl apply -f -
wait_for_pods $1-prepull
cat daemonset.yaml | sed "s/{{PHASE}}/$3/g" | sed "s/{{IMAGE_NAME}}/$1/g" | sed "s/{{TAG}}/$2/g" |  kubectl delete -f -

Reference

  • https://codefresh.io/kubernetes-tutorial/single-use-daemonset-pattern-pre-pulling-images-kubernetes/
  • https://etoews.github.io/blog/2017/05/13/run-once-daemonset-on-kubernetes/