ЁЯПл The SchoolтА║ЁЯУо CI/CDтА║ЁЯЪЪ рдзрдбрд╛ 10 тАФ CI рдордзреВрди Kubernetes рд╡рд░ deploy: delivery рд╡реНрд╣реЕрди
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЪЪ рдзрдбрд╛ 10 тАФ CI рдордзреВрди Kubernetes рд╡рд░ deploy: delivery рд╡реНрд╣реЕрди

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреАрд▓: lesson-09-deploy-strategies ┬╖ рдкреБрдвреАрд▓: lesson-11-four-dialects


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп рд╡реНрд╣реЕрдирдЪрд╛ рдкреВрд░реНрдг рдорд╛рд░реНрдЧ: day pass рдШреНрдпрд╛, рд╢рд╛рд│рд╛ рд╢реЛрдзрд╛, placeholder рдмрджрд▓рд╛, рд╕реВрдЪрдирд╛ рд▓рд╛рд╡рд╛, рддреА рд╡рд╛рдЪрдиреАрдп рд╣реЛрдИрдкрд░реНрдпрдВрдд рдерд╛рдВрдмрд╛, рдШрд░реА рдкрд░рдд рдЬрд╛. рдЦрд▒реНрдпрд╛ files:

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдлреЛрдЯреЛрдХреЙрдкреА рд▓реЙрдХрд░рдордзреНрдпреЗ ЁЯПж рдЖрд╣реЗрдд (рдзрдбрд╛ 07) рдЖрдгрд┐ рдкреНрд░рд╛рдЪрд╛рд░реНрдпрд╛рдВрдЪреНрдпрд╛ рд╕рд╣реАрдЪрд╛ рдирд┐рдпрдо рдард░рд▓рд╛ рдЖрд╣реЗ (рдзрдбрд╛ 08). рддрд░реАрд╣реА рдХреЛрдгрд╛рд▓рд╛ рддрд░реА рдЧрд╛рдбреА рдЪрд╛рд▓рд╡рдд рд╢рд╛рд│реЗрдд рдЬрд╛рд╡реЗ рд▓рд╛рдЧрддреЗ. ЁЯЪЪ

рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрдд рд╡реНрд╣реЕрди рддреНрдпрд╛рдЪ рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА рдХрд░рддреЗ:

  1. рдлрд╛рдЯрдХрд╛рд╡рд░ рдЖрдкрд▓рд╛ badge рджрд╛рдЦрд╡рддреЗ ЁЯкк тАФ glove box рдордзреНрдпреЗ master key рдирд╛рд╣реА. OIDC badge рдЪреНрдпрд╛ рдмрджрд▓реНрдпрд╛рдд day pass рдорд┐рд│рддреЛ (рдзрдбрд╛ 06), рдЖрдгрд┐ рд╢рд╛рд│реЗрдЪреНрдпрд╛ front desk рдЪреНрдпрд╛ рдпрд╛рджреАрдд рд╡реНрд╣реЕрдирдЪреЗ рдирд╛рд╡ рдЕрд╕рддреЗ.
  2. рд╢рд╛рд│рд╛ рдХреБрдареЗ рдЖрд╣реЗ рддреЗ рд╡рд┐рдЪрд╛рд░рддреЗ ЁЯЧ║я╕П тАФ aws eks update-kubeconfig рдкрддреНрддрд╛ рдЖрдгрд┐ рдлрд╛рдЯрдХрд╛рдЪрд╛ pass рдПрдХрд╛ kubeconfig file рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рддреЛ.
  3. рд╕реВрдЪрдирд╛ рд▓рд╛рд╡рддреЗ ЁЯУМ тАФ рдЖрдЬрдЪрд╛ copy рдХреНрд░рдорд╛рдВрдХ рд╕реВрдЪрдиреЗрд╡рд░ рд▓рд┐рд╣рд┐рддреЗ (sed IMAGE_PLACEHOLDER рдмрджрд▓рддреЛ), рдордЧ kubectl apply.
  4. рд╡рд╛рдЪрдиреАрдп рд╣реЛрдИрдкрд░реНрдпрдВрдд рдерд╛рдВрдмрддреЗ тП│ тАФ рдзрдбрд╛ 09 рдЪрд╛ rolling update рдкреВрд░реНрдг рд╣реЛрдИрдкрд░реНрдпрдВрдд kubectl rollout status рдЕрдбрд╡реВрди рдареЗрд╡рддреЛ, рдХрд┐рдВрд╡рд╛ рд╣рд╛рд░ рдорд╛рдиреВрди рдЕрдпрд╢рд╕реНрд╡реА delivery рдиреЛрдВрджрд╡рддреЛ. рд╕рд░рд╛рд╡ рдлрд▓рдХрд╛рд╡рд░ рддреА рдЬрд╛рдгрд╛рд▒реНрдпрд╛ рдПрдЦрд╛рджреНрдпрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрд▓рд╛ рд╕реВрдЪрдирд╛ рдореЛрдареНрдпрд╛рдиреЗ рд╡рд╛рдЪрд╛рдпрд▓рд╛рд╣реА рд╕рд╛рдВрдЧрддреЗ (cluster рдордзрд▓реА smoke test).

рдордЧ рддреА рдШрд░реА рдкрд░рдд рдЬрд╛рддреЗ. рдЖрдгрд┐ рдЗрдереЗрдЪ рдореЗрдЦ рдЖрд╣реЗ: рд╡реНрд╣реЕрди рдЧреЗрд▓реА рдХреА mailroom рдордзрд▓реЗ рдХреЛрдгреАрд╣реА рдлрд▓рдХрд╛рд╡рд░ рд▓рдХреНрд╖ рдареЗрд╡рдд рдирд╛рд╣реА. рдПрдЦрд╛рджреНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдиреЗ рд╣рд╛рддрд╛рдиреЗ pin рд╣рд▓рд╡рд▓рд╛, рддрд░ mailroom рд▓рд╛ рддреЗ рдкреБрдврдЪреНрдпрд╛ delivery рд▓рд╛ рдХрд│рддреЗ, рдХрд│рд▓реЗрдЪ рддрд░. рд╣рд╛ рд╡рд┐рдЪрд╛рд░ ArgoCD рд╢рд╛рд│реЗрд╕рд╛рдареА рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд╛.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    ship["ЁЯУж ship.yml<br/>image = тАж/hello-courier:sha"]
    badge["ЁЯкк configure-aws-credentials<br/>OIDC тЖТ day pass"]
    kc["ЁЯЧ║я╕П aws eks update-kubeconfig"]
    pin["ЁЯУМ sed IMAGE_PLACEHOLDER<br/>kubectl apply -n staging"]
    wait["тП│ kubectl rollout status<br/>--timeout=180s"]
    smoke["ЁЯзк kubectl run smoke<br/>curl hello-courier/healthz"]
    gate["ЁЯЫС environment: production<br/>required reviewers"]
    prod["ЁЯУМ same four moves<br/>-n production"]
    drift["ЁЯд╖ nobody watches the board<br/>тЖТ ArgoCD school"]
    ship -->|"1 workflow_call"| badge
    badge -->|"2"| kc
    kc -->|"3"| pin
    pin -->|"4"| wait
    wait -->|"5 exit 0"| smoke
    smoke -->|"6"| gate
    gate -->|"7 approved"| prod
    prod -.->|"8 the van drives away"| drift

тЭУ рдХрд╛рдп

ЁЯза рд╡реНрд╣реЕрдирдЪреНрдпрд╛ рдЪрд╛рд░ рдЪрд╛рд▓реА тАФ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдХрд╢рд╛рдореБрд│реЗ fail рд╣реЛрддреЗ

рдЪрд╛рд▓ рдХрд╛рдп рдЪрд╛рд▓рддреЗ job fail рд╣реЛрддреЛ рдЬреЗрд╡реНрд╣рд╛
ЁЯкк badge OIDC тЖТ рддрд╛рддреНрдкреБрд░рддреЗ credentials trust policy рдЪрд╛ sub рдпрд╛ repo рдЖрдгрд┐ branch рд╢реА рдЬреБрд│рдд рдирд╛рд╣реА
ЁЯЧ║я╕П рдкрддреНрддрд╛ aws eks update-kubeconfig role рдХрдбреЗ eks:DescribeCluster рдирд╛рд╣реА, рдХрд┐рдВрд╡рд╛ cluster рдиреЗ рддреЛ рдЬреЛрдбрд▓реЗрд▓рд╛ рдирд╛рд╣реА
ЁЯУМ рд▓рд╛рд╡рдгреЗ sed + kubectl apply -n <ns> -f k8s/ рдЕрд╡реИрдз YAML, рдХрд┐рдВрд╡рд╛ рдЬреЛрдбрд▓реЗрд▓реНрдпрд╛ identity рд▓рд╛ namespace рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рдгреНрдпрд╛рдЪреА рдкрд░рд╡рд╛рдирдЧреА рдирд╛рд╣реА
тП│ рдерд╛рдВрдмрдгреЗ kubectl rollout status --timeout=180s рдирд╡рд╛ pod рд╡реЗрд│реЗрдд /healthz рд▓рд╛ рдХрдзреАрдЪ рдЙрддреНрддрд░ рджреЗрдд рдирд╛рд╣реА

ЁЯдФ рдХрд╛

рдпрд╛ рдзрдбреНрдпрд╛рдЖрдзреА "deploy" рдореНрд╣рдгрдЬреЗ laptop рд╡рд░ kubeconfig рдЕрд╕рд▓реЗрд▓реА рдПрдХ рд╡реНрдпрдХреНрддреА тАФ рдХреБрдареЗрд╣реА рди рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ, рдкреБрдиреНрд╣рд╛ рдХрд░рд╛рдпрд▓рд╛ рдЕрд╡рдШрдб, рдЖрдгрд┐ рддреА file рдЬреНрдпрд╛рдЪреНрдпрд╛рдХрдбреЗ рдЕрд╕реЗрд▓ рддреНрдпрд╛рд▓рд╛ рдмрд╛рдВрдзрд▓реЗрд▓реЗ. рд╡реНрд╣реЕрдирдореБрд│реЗ рддреЗ рджрд░рд╡реЗрд│реА рддреНрдпрд╛рдЪ рдЪрд╛рд░ commands рд╣реЛрддреЗ, commit рд╢реЗрдЬрд╛рд░реА log рд╣реЛрддреЗ, рдзрдбрд╛ 08 рдЪреНрдпрд╛ approval рдорд╛рдЧреЗ. AWS рд╢рд╛рд│реЗрдЪрд╛ L05 рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ key рдкреЗрдХреНрд╖рд╛ badge рдХрд╛ рд╕рд░рд╕ рдЖрд╣реЗ рддреЗ рд╕рдордЬрд╛рд╡рддреЛ; ArgoCD рд╢рд╛рд│реЗрдЪрд╛ L03 рдиреЗрдордХреА рд╣реАрдЪ pipeline рдХрд╛рдврддреЛ, рдЖрдгрд┐ рддреНрдпрд╛рдЪреЗ L04 рдЖрдгрд┐ L05 job рд╕рдВрдкрд▓реНрдпрд╛рд╡рд░ рд╡реНрд╣реЕрдирд▓рд╛ рдХрд╛рдп рджрд┐рд╕реВ рд╢рдХрдд рдирд╛рд╣реА рдЖрдгрд┐ рддреНрдпрд╛рдмрджреНрджрд▓ рдХрд╛рдп рдХрд░рд╛рдпрдЪреЗ рддреЗ рдореЛрдЬрддрд╛рдд.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

deploy.yml рдЪрд╛ рдкреВрд░реНрдг staging job:

jobs:
  staging:
    runs-on: ubuntu-latest
    environment: staging                          # the practice notice board
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with: { role-to-assume: "${{ vars.AWS_ROLE_ARN }}", aws-region: "${{ vars.AWS_REGION }}" }
      - name: ЁЯФз point kubectl at the cluster
        run: aws eks update-kubeconfig --region "${{ vars.AWS_REGION }}" --name "${{ vars.EKS_CLUSTER }}"
      - name: ЁЯУМ pin the new notice (rolling update, lesson 09)
        run: |
          sed -i "s|IMAGE_PLACEHOLDER|${{ inputs.image }}|" k8s/deployment.yaml
          kubectl apply -n staging -f k8s/
          kubectl rollout status -n staging deployment/hello-courier --timeout=180s
      - name: ЁЯзк smoke-test staging from inside the cluster
        run: kubectl run -n staging smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/healthz

production job рддреНрдпрд╛рдЪ steps -n production рд╕рд╣, environment: production рдорд╛рдЧреЗ рдкреБрдиреНрд╣рд╛ рдХрд░рддреЛ. рдЕрдзрд┐рдХ рдиреАрдЯрдиреЗрдЯрдХреЗ placeholder рдмрджрд▓ рдЕрд╕реЗ рджрд┐рд╕рддрд╛рдд:

# illustrative тАФ this repo ships neither a kustomization.yaml nor a Helm chart
kustomize edit set image hello-courier=123456789012.dkr.ecr.ap-south-1.amazonaws.com/hello-courier:$GITHUB_SHA
helm upgrade --install hello-courier ./chart --set image.tag=$GITHUB_SHA
ЁЯФБ рд╣реЗрдЪ CircleCI рдордзреНрдпреЗ
  deploy-to-eks:
    parameters:
      namespace:
        type: string
    docker:
      - image: cimg/aws:2024.03
    steps:
      - checkout
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_DEFAULT_REGION}
      - run:
          name: ЁЯУМ apply and wait for the rollout
          command: |
            IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${ECR_REPOSITORY}:${CIRCLE_SHA1}"
            sed -i "s|IMAGE_PLACEHOLDER|${IMAGE}|" k8s/deployment.yaml
            kubectl apply -n << parameters.namespace >> -f k8s/
            kubectl rollout status -n << parameters.namespace >> deployment/hello-courier --timeout=180s

рдПрдХрдЪ parameterized job, workflows: рдордзреВрди рджреЛрдирджрд╛ рдмреЛрд▓рд╛рд╡рд▓реЗрд▓рд╛ тАФ namespace: staging рд╕рд╣ deploy-staging, рдЖрдгрд┐ hold-for-approval рдирдВрддрд░ namespace: production рд╕рд╣ deploy-production.

ЁЯжК рд╣реЗрдЪ GitLab CI рдордзреНрдпреЗ
.deploy: &deploy
  stage: deploy
  image: amazon/aws-cli:2.17.0
  <<: *aws-badge
  script:
    - aws eks update-kubeconfig --region "$AWS_REGION" --name "$EKS_CLUSTER"
    - IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${CI_COMMIT_SHA}"
    - sed -i "s|IMAGE_PLACEHOLDER|${IMAGE}|" k8s/deployment.yaml
    - kubectl apply -n "$NAMESPACE" -f k8s/
    - kubectl rollout status -n "$NAMESPACE" deployment/hello-courier --timeout=180s

deploy-staging:
  <<: *deploy
  variables: { NAMESPACE: staging }
  environment: { name: staging }

.deploy anchor рдореНрд╣рдгрдЬреЗ рд╡реНрд╣реЕрди; рдкреНрд░рддреНрдпреЗрдХ environment рдореНрд╣рдгрдЬреЗ рддреЛ anchor рдЕрдзрд┐рдХ рдПрдХ NAMESPACE рдЖрдгрд┐ рдПрдХ environment: рдирд╛рд╡.

ЁЯОй рд╣реЗрдЪ Jenkins рдордзреНрдпреЗ
def deployTo(String namespace) {
  withAWS(credentials: 'aws-school-cicd', region: env.AWS_REGION) {
    sh """
      ACCOUNT=\$(aws sts get-caller-identity --query Account --output text)
      IMAGE="\${ACCOUNT}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.ECR_REPOSITORY}:${env.GIT_COMMIT}"
      aws eks update-kubeconfig --region ${env.AWS_REGION} --name ${env.EKS_CLUSTER}
      sed -i "s|IMAGE_PLACEHOLDER|\${IMAGE}|" k8s/deployment.yaml
      kubectl apply -n ${namespace} -f k8s/
      kubectl rollout status -n ${namespace} deployment/hello-courier --timeout=180s
    """
  }
}

deployTo('staging') рдореНрд╣рдгреВрди рдмреЛрд▓рд╛рд╡рд▓рд╛ рдЬрд╛рддреЛ, рдЖрдгрд┐ input stage рдирдВрддрд░ deployTo('production') рдореНрд╣рдгреВрди.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

рд╡реНрд╣реЕрдирдЪрд╛ рдорд╛рд░реНрдЧ, рд╕реНрдерд╛рдирд┐рдХ cluster рд╡рд░ рд╣рд╛рддрд╛рдиреЗ рдЪрд╛рд▓рд╡рд▓реЗрд▓рд╛. рдлрдХреНрдд AWS рдЕрд╢реА рдЦреВрдг рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдУрд│реАрдВрдирд╛ account рд▓рд╛рдЧрддреЗ; рдмрд╛рдХреА kind рдХрд┐рдВрд╡рд╛ Docker Desktop рд╡рд░ рдЪрд╛рд▓рддреЗ.

# 0) local cluster + both boards (skip the first line if lesson 09's cluster is still up)
kind create cluster --name school
kubectl create namespace staging; kubectl create namespace production

# 1) the photocopy тАФ ship.yml's build job, locally (CI tags with the full SHA)
docker build --build-arg APP_VERSION=$(git rev-parse --short HEAD) -t hello-courier:local app
kind load docker-image hello-courier:local --name school   # kind only; Docker Desktop shares its daemon

# 2) the van as a loop: staging, a signature, production
IMAGE=hello-courier:local
for NS in staging production; do
  echo "ЁЯЪЪ delivering $IMAGE to $NS"
  # AWS only: aws-actions/configure-aws-credentials        тЖТ locally, your kubeconfig already exists
  # AWS only: aws eks update-kubeconfig --region ap-south-1 --name school-eks
  sed "s|IMAGE_PLACEHOLDER|$IMAGE|" k8s/deployment.yaml | kubectl apply -n "$NS" -f -   # CI: sed -i on its throwaway checkout
  kubectl apply -n "$NS" -f k8s/service.yaml
  kubectl rollout status -n "$NS" deployment/hello-courier --timeout=180s || { echo "тЭМ failed delivery"; break; }
  kubectl run -n "$NS" smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/healthz
  [ "$NS" = staging ] && { echo "ЁЯЫС principal: press Enter to approve production"; read -r _; }
done

# 3) AWS only тАФ the real van: set the four repo variables from ship.yml's header, then
gh workflow run deploy.yml -f image=123456789012.dkr.ecr.ap-south-1.amazonaws.com/hello-courier:<full-sha>
gh run watch

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

тПня╕П рдкреБрдвреЗ

рд╡реНрд╣реЕрдирдиреЗ рдЖрддрд╛ GitHub Actions рдордзреНрдпреЗ рдкреВрд░реНрдг рдорд╛рд░реНрдЧ рдкрд╛рд░ рдХреЗрд▓рд╛. рдзрдбрд╛ 11 CircleCI, GitLab рдЖрдгрд┐ Jenkins рдЪреНрдпрд╛ рд╡реНрд╣реЕрди рддрд┐рдЪреНрдпрд╛ рд╢реЗрдЬрд╛рд░реА рдЙрднреНрдпрд╛ рдХрд░рддреЛ рдЖрдгрд┐ рддреБрдореНрд╣рд╛рд▓рд╛ рд╢рдмреНрджрдХреЛрд╢ рджреЗрддреЛ.

git checkout lesson-11-four-dialects

ЁЯЪЪ Lesson 10 тАФ Deploy to Kubernetes from CI: the delivery van

ЁЯУН You are here: Lesson 10 of 12 ┬╖ Previous: lesson-09-deploy-strategies ┬╖ Next: lesson-11-four-dialects


ЁЯУж What's in this branch

Lessons 01тАУ09, plus the van's whole route: get a day pass, find the school, swap the placeholder, pin the notice, wait until it is readable, drive home. Real files:

ЁЯзТ Explain like I'm 5

The photocopies are in the locker ЁЯПж (lesson 07) and the principal's signature rule is set (lesson 08). Someone still has to drive to school. ЁЯЪЪ

The van does the same four things on every trip:

  1. Shows its badge at the gate ЁЯкк тАФ no master key in the glove box. The OIDC badge buys a day pass (lesson 06), and the school's front desk has the van's name on its list.
  2. Asks where the school is ЁЯЧ║я╕П тАФ aws eks update-kubeconfig writes the address and the gate pass into a kubeconfig file.
  3. Pins the notice ЁЯУМ тАФ writes today's copy number onto the notice (sed swaps IMAGE_PLACEHOLDER), then kubectl apply.
  4. Waits until it is readable тП│ тАФ kubectl rollout status blocks until lesson 09's rolling update finishes, or gives up and reports a failed delivery. On the practice board it also asks a passing student to read the notice back (the in-cluster smoke test).

Then it drives home. And here is the catch: once the van is gone, nobody in the mailroom is watching the board. If a teacher moves a pin by hand, the mailroom finds out at the next delivery, if at all. Hold that thought for the ArgoCD school.

ЁЯЧ║я╕П Diagram

flowchart LR
    ship["ЁЯУж ship.yml<br/>image = тАж/hello-courier:sha"]
    badge["ЁЯкк configure-aws-credentials<br/>OIDC тЖТ day pass"]
    kc["ЁЯЧ║я╕П aws eks update-kubeconfig"]
    pin["ЁЯУМ sed IMAGE_PLACEHOLDER<br/>kubectl apply -n staging"]
    wait["тП│ kubectl rollout status<br/>--timeout=180s"]
    smoke["ЁЯзк kubectl run smoke<br/>curl hello-courier/healthz"]
    gate["ЁЯЫС environment: production<br/>required reviewers"]
    prod["ЁЯУМ same four moves<br/>-n production"]
    drift["ЁЯд╖ nobody watches the board<br/>тЖТ ArgoCD school"]
    ship -->|"1 workflow_call"| badge
    badge -->|"2"| kc
    kc -->|"3"| pin
    pin -->|"4"| wait
    wait -->|"5 exit 0"| smoke
    smoke -->|"6"| gate
    gate -->|"7 approved"| prod
    prod -.->|"8 the van drives away"| drift

тЭУ What

ЁЯза The van's four moves тАФ and what fails each one

move what runs the job fails when
ЁЯкк badge OIDC тЖТ temporary credentials the trust policy's sub does not match this repo and branch
ЁЯЧ║я╕П address aws eks update-kubeconfig the role lacks eks:DescribeCluster, or the cluster does not map it
ЁЯУМ pin sed + kubectl apply -n <ns> -f k8s/ invalid YAML, or the mapped identity may not write to the namespace
тП│ wait kubectl rollout status --timeout=180s the new pod never answers /healthz in time

ЁЯдФ Why

Before this lesson, "deploy" meant a person with a kubeconfig on a laptop тАФ undocumented, hard to repeat, and tied to whoever holds the file. The van makes it the same four commands each time, logged next to the commit, behind the approval from lesson 08. The AWS school's L05 explains why the badge beats a stored key; the ArgoCD school's L03 draws this exact pipeline, and its L04 and L05 count what the van cannot see once the job ends and what to do about it.

ЁЯФз How (in this repo)

The whole staging job of deploy.yml:

jobs:
  staging:
    runs-on: ubuntu-latest
    environment: staging                          # the practice notice board
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with: { role-to-assume: "${{ vars.AWS_ROLE_ARN }}", aws-region: "${{ vars.AWS_REGION }}" }
      - name: ЁЯФз point kubectl at the cluster
        run: aws eks update-kubeconfig --region "${{ vars.AWS_REGION }}" --name "${{ vars.EKS_CLUSTER }}"
      - name: ЁЯУМ pin the new notice (rolling update, lesson 09)
        run: |
          sed -i "s|IMAGE_PLACEHOLDER|${{ inputs.image }}|" k8s/deployment.yaml
          kubectl apply -n staging -f k8s/
          kubectl rollout status -n staging deployment/hello-courier --timeout=180s
      - name: ЁЯзк smoke-test staging from inside the cluster
        run: kubectl run -n staging smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/healthz

The production job repeats the same steps with -n production behind environment: production. The tidier placeholder swaps look like this:

# illustrative тАФ this repo ships neither a kustomization.yaml nor a Helm chart
kustomize edit set image hello-courier=123456789012.dkr.ecr.ap-south-1.amazonaws.com/hello-courier:$GITHUB_SHA
helm upgrade --install hello-courier ./chart --set image.tag=$GITHUB_SHA
ЁЯФБ The same thing in CircleCI
  deploy-to-eks:
    parameters:
      namespace:
        type: string
    docker:
      - image: cimg/aws:2024.03
    steps:
      - checkout
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_DEFAULT_REGION}
      - run:
          name: ЁЯУМ apply and wait for the rollout
          command: |
            IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${ECR_REPOSITORY}:${CIRCLE_SHA1}"
            sed -i "s|IMAGE_PLACEHOLDER|${IMAGE}|" k8s/deployment.yaml
            kubectl apply -n << parameters.namespace >> -f k8s/
            kubectl rollout status -n << parameters.namespace >> deployment/hello-courier --timeout=180s

One parameterized job, called twice from workflows: тАФ deploy-staging with namespace: staging, and deploy-production with namespace: production after hold-for-approval.

ЁЯжК The same thing in GitLab CI
.deploy: &deploy
  stage: deploy
  image: amazon/aws-cli:2.17.0
  <<: *aws-badge
  script:
    - aws eks update-kubeconfig --region "$AWS_REGION" --name "$EKS_CLUSTER"
    - IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${CI_COMMIT_SHA}"
    - sed -i "s|IMAGE_PLACEHOLDER|${IMAGE}|" k8s/deployment.yaml
    - kubectl apply -n "$NAMESPACE" -f k8s/
    - kubectl rollout status -n "$NAMESPACE" deployment/hello-courier --timeout=180s

deploy-staging:
  <<: *deploy
  variables: { NAMESPACE: staging }
  environment: { name: staging }

The .deploy anchor is the van; each environment is the anchor plus a NAMESPACE and an environment: name.

ЁЯОй The same thing in Jenkins
def deployTo(String namespace) {
  withAWS(credentials: 'aws-school-cicd', region: env.AWS_REGION) {
    sh """
      ACCOUNT=\$(aws sts get-caller-identity --query Account --output text)
      IMAGE="\${ACCOUNT}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.ECR_REPOSITORY}:${env.GIT_COMMIT}"
      aws eks update-kubeconfig --region ${env.AWS_REGION} --name ${env.EKS_CLUSTER}
      sed -i "s|IMAGE_PLACEHOLDER|\${IMAGE}|" k8s/deployment.yaml
      kubectl apply -n ${namespace} -f k8s/
      kubectl rollout status -n ${namespace} deployment/hello-courier --timeout=180s
    """
  }
}

Called as deployTo('staging'), and after the input stage as deployTo('production').

ЁЯзк Try it

The van's route, driven by hand against a local cluster. Only the lines marked AWS need an account; the rest runs on kind or Docker Desktop.

# 0) local cluster + both boards (skip the first line if lesson 09's cluster is still up)
kind create cluster --name school
kubectl create namespace staging; kubectl create namespace production

# 1) the photocopy тАФ ship.yml's build job, locally (CI tags with the full SHA)
docker build --build-arg APP_VERSION=$(git rev-parse --short HEAD) -t hello-courier:local app
kind load docker-image hello-courier:local --name school   # kind only; Docker Desktop shares its daemon

# 2) the van as a loop: staging, a signature, production
IMAGE=hello-courier:local
for NS in staging production; do
  echo "ЁЯЪЪ delivering $IMAGE to $NS"
  # AWS only: aws-actions/configure-aws-credentials        тЖТ locally, your kubeconfig already exists
  # AWS only: aws eks update-kubeconfig --region ap-south-1 --name school-eks
  sed "s|IMAGE_PLACEHOLDER|$IMAGE|" k8s/deployment.yaml | kubectl apply -n "$NS" -f -   # CI: sed -i on its throwaway checkout
  kubectl apply -n "$NS" -f k8s/service.yaml
  kubectl rollout status -n "$NS" deployment/hello-courier --timeout=180s || { echo "тЭМ failed delivery"; break; }
  kubectl run -n "$NS" smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/healthz
  [ "$NS" = staging ] && { echo "ЁЯЫС principal: press Enter to approve production"; read -r _; }
done

# 3) AWS only тАФ the real van: set the four repo variables from ship.yml's header, then
gh workflow run deploy.yml -f image=123456789012.dkr.ecr.ap-south-1.amazonaws.com/hello-courier:<full-sha>
gh run watch

тЪая╕П Common mistakes

тПня╕П Next

The van has now driven the full route in GitHub Actions. Lesson 11 parks the CircleCI, GitLab and Jenkins vans next to it and hands you the phrasebook.

git checkout lesson-11-four-dialects
тЖР Previousdeploy strategiesNext тЖТfour dialects

This page is the lesson's README from the lesson-10-deploy-to-kubernetes branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.