Skip to content

Latest commit

 

History

History

09-cicd

CI/CD

1. GitHub Actions

1.1. GitHub Actions: Build and push image

  1. .github/workflows/deploy-pr.yaml in application repository e.g. nakamasato/fastapi-sample#97
  2. name: Job name e.g. deploy-pr
  3. on: trigger of the workflow
    1. Specify pull_request with paths
  4. jobs: define one or more jobs
    1. Create a new job with id build-and-push-image
    2. runs-on: define machine type for the job. e.g. ubuntu-latest
    3. steps: series of steps in the job
      1. name: (optional) step name. you can give any arbitrary name. e.g. Checkout repository
      2. uses: specify which actions to use. e.g. actions/checkout@v4, docker/build-push-action@v5, ...
      3. with: Actions' configuration. The input value is different from each GitHub Actions.
name: deploy-pr

on:
  pull_request:
    paths:
      - 'app/**'
      - .github/workflows/deploy-pr.yml

env:
  REGISTRY: ghcr.io

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ github.repository }} # ghcr.io/nakamasato/fastapi-sample

      - name: Log in to GitHub container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: app
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

1.2. GitHub Actions: Create a Pull Request to update a manifest

  1. Add a new job update-manifest-file.
  2. In the new job, we need to reference the new docker image name built in the above job build-and-push-image.
    1. outputs: key-value map that can be referenced by subsequent jobs.
      1. Set outputs in the job build-and-push-image
        outputs:
          IMAGE_FULL_NAME: ${{ steps.meta.outputs.tags }}
    2. needs: specify a list of the ids of dependent jobs.
      needs: [build-and-push-image]
    3. Add a step to update manifest file.
      - name: Update fastapi-sample image
        env:
          IMAGE_FULL_NAME: ${{ needs.build-and-push-image.outputs.IMAGE_FULL_NAME }}
        run: |
          yq -i "(.spec.template.spec.containers[] | select(.name == \"fastapi-sample\")).image = \"$IMAGE_FULL_NAME\"" manifests/fastapi-sample/deployment.yaml
    4. Add a step to create a pr with peter-evans/create-pull-request@v5
      - name: Create PR
        uses: peter-evans/create-pull-request@v5
        with:
          base: main
          title: "Update fastapi-sample"
          draft: true
          body: |
            # Changes
            - Update `fastapi-sample` image to ${{ needs.build-and-push-image.outputs.IMAGE_FULL_NAME }}
Full yaml
name: deploy-pr

on:
  pull_request:
    paths:
      - 'app/**'
      - .github/workflows/deploy-pr.yml

env:
  REGISTRY: ghcr.io

jobs:
  build-and-push-image:
    outputs:
      IMAGE_FULL_NAME: ${{ steps.meta.outputs.tags }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ github.repository }} # ghcr.io/nakamasato/fastapi-sample

      - name: Log in to GitHub container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: app
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  update-kubernetes-manifest:
    runs-on: ubuntu-latest
    needs: [build-and-push-image]
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Update fastapi-sample image
        env:
          IMAGE_FULL_NAME: ${{ needs.build-and-push-image.outputs.IMAGE_FULL_NAME }}
        run: |
          yq -i "(.spec.template.spec.containers[] | select(.name == \"fastapi-sample\")).image = \"$IMAGE_FULL_NAME\"" manifests/fastapi-sample/deployment.yaml
      - name: Create PR
        uses: peter-evans/create-pull-request@v5
        with:
          base: main
          title: "Update fastapi-sample"
          draft: true
          body: |
            # Changes
            - Update `fastapi-sample` image to ${{ needs.build-and-push-image.outputs.IMAGE_FULL_NAME }}

2.1. Setup ArgoCD

  1. Install ArgoCD (Version v2.8.4)

    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.8.4/manifests/install.yaml
    
  2. Check

    kubectl get pod -n argocd
    NAME                                                READY   STATUS    RESTARTS   AGE
    argocd-application-controller-0                     1/1     Running   0          48s
    argocd-applicationset-controller-787bfd9669-xh6hh   1/1     Running   0          48s
    argocd-dex-server-bb76f899c-c92zj                   1/1     Running   0          48s
    argocd-notifications-controller-5557f7bb5b-mmpzv    1/1     Running   0          48s
    argocd-redis-b5d6bf5f5-zl6zm                        1/1     Running   0          48s
    argocd-repo-server-56998dcf9c-bz875                 1/1     Running   0          48s
    argocd-server-5985b6cf6f-44s47                      1/1     Running   0          48s
    
  3. Expose port

    Either of the following methods:

    1. Create Service with NodePort type (port: 30080)

      kubectl apply -f argocd-server-node-port.yaml -n argocd
      
    2. Port forward the service (port: 30080)

      kubectl -n argocd port-forward service/argocd-server 30080:80
      
  4. Login

    Open https://localhost:30080, click on Advanced and Proceed to localhost (unsafe) (this is ok because we're connecting to the argocd running in our local computer)

    • username: admin
    • password: kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 --decode

2.2. Deploy an application using ArgoCD

  1. Create AppProject

    kubectl apply -f argocd-appproject-test.yaml
    
  2. Create Application

    kubectl apply -f argocd-application-sample-app.yaml
    

    If you're updating manifests in https://github.com/nakamasato/fastapi-sample/tree/main/manifests/fastapi-sample

     spec:
       project: test
       source:
    -    repoURL: https://github.com/nakamasato/kubernetes-basics.git # Forkしている場合はnakamasatoを自分のGithubアカウントに変更してく
    ださい
    -    targetRevision: v2.1.1
    -    path: 09-cicd/sample-app-manifests
    +    repoURL: https://github.com/nakamasato/fastapi-sample.git # Forkしている場合はnakamasatoを自分のGithubアカウントに変更してください
    +    path: manifests/fastapi-sample
  3. Deploy MySQL

    As you can see in Run simple application in kubernetes, sample-app is dependent on mysql. So you need to run mysql in default namespace by

    kubectl apply -f mysql-manifests
    

    Exercise: Please create an ArgoCD application to sync MySQL.

2.3. Check Appications on ArgoCD UI

  1. Open https://localhost:30080/

  2. Click on the sample-app application.

    You can see all the resources applied by ArgoCD are in healthy state.

2.4. Deploy with/without finalizer

  1. Delete ArgoCD application (with finalizer)

    kubectl delete -f argocd-application-sample-app.yaml
    

    All the resources created by ArgoCD are deleted.

    kubectl get pod # same for service, secret, configmap
  2. Create application without the finalizer

    - finalizers: # Cascading Deletion https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/#about-the-deletion-finalizer
    -   - resources-finalizer.argocd.argoproj.io
    kubectl apply -f argocd-application-sample-app.yaml
    
    kubectl get pod
    
  3. Delete application

    kubectl delete -f argocd-application-sample-app.yaml
    

    The resources deployed by ArgoCD still exist

    kubectl get pod,svc,secret,cm
    

4. Update fastapi-sample and apply change with ArgoCD

  1. Check the current fastapi-sample

    kubectl port-forward svc/sample-app 8080:80
    

    http://localhost:8080 -> We'll see {"message":"Hello World"}

  2. Update fastapi-sample

    Hello World -> こんにちは

  3. Push the change to the branch on which we created deploy-pr.yaml

    1. A new Docker image will be built and pushed to GitHub Container Registry.
    2. Update the image and create a Pull Request. e.g. nakamasato/fastapi-sample#98
  4. Merge the Pull Request e.g. nakamasato/fastapi-sample#98

  5. ArgoCD will sync the change to the Kubernetes cluster.

  6. Check the fastapi-sample

    kubectl port-forward svc/sample-app 8080:80
    

    http://localhost:8080 -> We'll see {"message":"こんにちは"}

3. Clean up

  1. Delete MySQL

    kubectl delete -f mysql-manifests
    
  2. Delete ArgoCD Application and AppProject.

    kubectl delete -f argocd-application-sample-app.yaml
    kubectl delete -f argocd-appproject-test.yaml
    
  3. Uninstall ArgoCD and delete the argocd namespace.

    kubectl delete -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.8.4/manifests/install.yaml
    kubectl delete ns argocd
    

4. Optional Topic: Create a GitHub Actions to update PR

In the example above, we used the same repository fastapi-sample for application codes and the manifest yaml files. In this example, you will create a GitHub Actions workflow in application repository that updates manifest file in the manifest repository.

  1. Fork the sample-app and kubernetes-basics repos

  2. Generate a Personal Access Token (PAT) and configure it in the forked fastapi-sample repo.

    1. Open you GitHub Settings > Developer Settings > Personal Access Token

      Settings

      Developer settings

    2. Create Personal Access Token

    3. Set the personal access token in the secrets of fastapi-sample repo.

      Open https://github.com/<<your_github_name>>/fastapi-sample/settings/secrets/actions

    This is necessary to create a PR in the forked kubernetes-basics repo.

    For more details about Personal Access Token, please read Personal Access Token

  3. Add .github/workflows/deploy-pr.yml on a new branch.

    Please replace <yourgithubname> with your github name.

    name: deploy-pr
    
    on:
      pull_request:
    
    env:
      REGISTRY: ghcr.io
      IMAGE_NAME: ${{ github.repository }}
    
    jobs:
      # https://docs.github.com/ja/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#publishing-a-package-using-an-action
      build-and-push-image:
        runs-on: ubuntu-latest
        outputs: # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idoutputs
          tags: ${{ steps.meta.outputs.tags }}
    
        steps:
          - name: Checkout repository
            uses: actions/checkout@v4
    
          - name: Log in to the Container registry
            uses: docker/login-action@v3
            with:
              registry: ${{ env.REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          - name: Extract metadata (tags, labels) for Docker
            id: meta
            uses: docker/metadata-action@v5
            with:
              images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
    
          - name: Build and push Docker image
            uses: docker/build-push-action@v5
            with:
              context: app
              push: true
              tags: ${{ steps.meta.outputs.tags }}
              labels: ${{ steps.meta.outputs.labels }}
      update-manifest:
        needs: build-and-push-image
        runs-on: ubuntu-latest
        steps:
          - name: Checkout <yourgithubname>/kubernetes-basics #https://github.com/nakamasato/kubernetes-basics/blob/v2.0-rc/09-cicd/sample-app-manifests/deployment.yaml
            uses: actions/checkout@v4
            with:
              repository: <yourgithubname>/kubernetes-basics # レクチャーと同じように試したい場合には、 Forkして <your github account>/kubernetes-basics というふうに変更してください
              ref: v2.0-rc
          - name: Update manifest file
            env:
              YAML_PATH: 09-cicd/sample-app-manifests/deployment.yaml
              CONTAINER_NAME: fastapi-sample
              IMAGE_FULL_NAME: ${{ needs.build-and-push-image.outputs.tags }} # pull_request -> ghcr.io/nakamasato/fastapi-sample:pr-{pull-request-num}
            run: |
              yq e -i "(.spec.template.spec.containers[]|select(.name == \"${CONTAINER_NAME}\").image)|=\"${IMAGE_FULL_NAME}\"" ${YAML_PATH}
              cat ${YAML_PATH}
          - name: Create PR
            uses: peter-evans/create-pull-request@v5
            with:
              token: ${{ secrets.REPO_GITHUB_TOKEN }} # レクチャー内では、Personal Access Tokenを発行して fastapi-sampleレポから kubernetes-basicsレポコードをプッシュできる権限を与えました。
              title: "Update fastapi-sample"
              base: v2.0-rc
              draft: true
              body: |
                # Changes
                - Update `fastapi-sample` image
    1. About outputs in job: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs
    2. GitHub Actions to create a PR: https://github.com/peter-evans/create-pull-request
  4. Push the commit that includes the .github/workflows/deploy-pr.yaml and create a pr.

    1. Example PR: nakamasato/fastapi-sample#55
    2. PR will trigger GitHub Actions job to create a PR in kubernetes-basics repo: #17
  5. Merge the created PR in kubernetes-basics repo and ArgoCD will apply the change.