DEV Community

Atsushi Miyamoto
Atsushi Miyamoto

Posted on • Updated on

Kubernetes CI/CD With GHA and ArgoCD

Nowadays, most companies try to implement CI/CD pipelines to deploy applications to be easier. But how to implement it?
This article shows how to implement it to deploy your application on Kubernetes with GitHub Actions and ArgoCD.
I hope it can help your deployment process easier.

Before starting, Let me explain about what is GitOps. Because I will follow this methodology.

What is GitOps?

GitOps is an operational framework that takes DevOps best practices used for application development such as version control, collaboration, compliance, and CI/CD tooling, and applies them to infrastructure automation. GitOps was first coined by Weaveworks.

Based on that I will implement like below CI/CD.

Gitops

Let's start building the CI/CD!

There are 5 steps to deploy your application on Kubernetes with GitHub Actions and ArgoCD.

  1. Build CI
    1. Login to ECR
    2. Build docker image and push it to ECR
    3. Update manifest file in manifest file repository
    4. Create PR
  2. Prepare Manifest file
  3. Build CD
  4. Recap 5.

1. Build CI

The first step is building the CI by using Github Actions. yaml file should be in application source code repository.

During CI, we can do testing, building docker, push docker image to repository and update manifest file!
You must create ECR on AWS and IAM role to login and push docker image to ECR. I will omit these part in this time.

Complete GHA file is below. I will explain each step by step.

on:
  push:
    branches: [deploy/stg]

permissions:
  id-token: write
  contents: read

jobs:
  prepare:
    name: code-deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.IAM_ROLE }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: login to Amazon ECR
        id: ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: docker build and push
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -f ./Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --target release .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: fetch manifest repository
        uses: actions/checkout@v3
        with:
          repository: github-organization/manifest-repository
          token: ${{ secrets.PAT }}
          path: manifest-repo
          fetch-depth: 1

      - name: update manifest file image tag
        run: |
          wget -q https://github.com/mikefarah/yq/releases/download/v4.27.5/yq_linux_amd64
          sudo mv yq_linux_amd64 /usr/local/bin/yq
          sudo chmod +x /usr/local/bin/yq
          yq e -i '.spec.template.spec.containers[0].image |= "${{ steps.build-image.outputs.image }}"' 'manifest-repo/k8s/sample/dev/deployment.yaml'

      - name: setup Repository
        working-directory: manifest-repo
        run: |
          git fetch origin main
          git config --global user.name "github-actions[bot]"
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git remote set-url --push origin https://github.com/***/manifest-repo.git
          git checkout -b release-${{ github.sha }}

      - name: commit
        working-directory: manifest-repo
        run: |
          git add .
          git commit -m "Release bff-admin"
          git push origin HEAD
      - name: create PR
        working-directory: manifest-repo
        run: |
          gh pr create -B main -H release-${{ github.sha }} -t "deploy-${{ github.sha }}" -b ""
Enter fullscreen mode Exit fullscreen mode
  1. Login to ECR The security reason, you must avoid to pass your access key and secret key. Instead pass credential, we can use GitHub's OIDC provider in conjunction with a configured AWS IAM Identity Provider endpoint. You must create Assume role and set it to role-to-assume
      - name: configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: ${{ secrets.IAM_ROLE }}
        aws-region: ${{ secrets.AWS_REGION }}

    - name: login to Amazon ECR
      id: ecr
      uses: aws-actions/amazon-ecr-login@v1
Enter fullscreen mode Exit fullscreen mode

ref: aws-actions/configure-aws-credentials

  1. Build docker image and push it to ECR The next step, build a docker image and push it to ECR. I prefer to import the registry name and repository name from secret. After setting these variables, run the docker command to build and push to ECR.
    - name: docker build and push
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.ecr.outputs.registry }}
        ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        docker build -f ./Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --target target .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

Enter fullscreen mode Exit fullscreen mode
  1. Update manifest file in manifest file repository After pushing the docker image, do update the manifest file to create PR to the manifest repository. Assume the manifest repository manages your k8s manifest files. At the first, fetch the latest code from the manifest repository, and after that update file using yq. We just need to update the container image to the newest image which we uploaded to ECR in the previous step.
      - name: fetch manifest repository
        uses: actions/checkout@v3
        with:
          repository: github-organization/manifest-repository
          token: ${{ secrets.PAT }}
          path: manifest-repo
          fetch-depth: 1

      - name: update manifest file image tag
        run: |
          wget -q https://github.com/mikefarah/yq/releases/download/v4.27.5/yq_linux_amd64
          sudo mv yq_linux_amd64 /usr/local/bin/yq
          sudo chmod +x /usr/local/bin/yq
          yq e -i '.spec.template.spec.containers[0].image |= "${{ steps.build-image.outputs.image }}"' 'manifest-repo/k8s/sample/dev/deployment.yaml'

Enter fullscreen mode Exit fullscreen mode
  1. Create PR After updating the manifest file, finally, create PR to trigger CD. Merging PR can be your deploy your latest code to the production environment. I use gh to create PR.
      - name: commit
      working-directory: manifest-repo
      run: |
        git add .
        git commit -m "Release bff-admin"
        git push origin HEAD
    - name: create PR
      working-directory: manifest-repo
      run: |
        gh pr create -B main -H release-${{ github.sha }} -t "deploy-${{ github.sha }}" -b ""

Enter fullscreen mode Exit fullscreen mode

2. Prepare Manifest file

Prepare the manifest file in the manifest repository. It will update by CI.
Below is a sample manifest file.
You must create namespace first. This time I create deploy-test.

manifest-repository/k8s/deploy-test/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-test
  namespace: deploy-test
spec:
  selector:
    matchLabels:
      app: deploy-test
  template:
    metadata:
      labels:
        app: deploy-test
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: eks.amazonaws.com/nodegroup
                operator: In
                values:
                  - sample-ng
      containers:
        - name: deploy-test
          image: yourAccountId.dkr.ecr.yourRegion.amazonaws.com/yourRepositoryName:yourImageTag
Enter fullscreen mode Exit fullscreen mode

Remember, during CI, I use yq to update the image tag. the update part is yourImageTag.
So your pod will pull the latest image from ECR after PR is merged.

    containers:
        - name: deploy-test
          image: yourAccountId.dkr.ecr.yourRegion.amazonaws.com/yourRepositoryName:yourImageTag
Enter fullscreen mode Exit fullscreen mode

3. Build CD

Finally, build a CD using ArgoCD!

*You must create namespace and cluster.

The first step is to set up a GitHub access token to access the cluster to your repository.

You can type the below cmd to set your GitHub token to your cluster.
This time namespace that I set is argocd.

export GITHUB_TOKEN=<github-token>
kubectl create secret generic github-cred -n argocd \
  --from-literal url=https://github.com/${GITHUB_USER}/manifest-repository \
  --from-literal username=<github-user> \
  --from-literal password=${GITHUB_TOKEN} \
  --from-literal name=github-cred
kubectl label secret task-tool-cluster-config-cred argocd.argoproj.io/secret-type=repository -n argoc
Enter fullscreen mode Exit fullscreen mode

Then write argocd manifest file.
file path is manifest-repository/k8s/deploy-test/argocd/deploy.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: deploy-test
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/${GITHUB_USER}/manifest-repository
    targetRevision: main
    path: k8s/deploy-test
  syncPolicy:
    automated:
      selfHeal: true
      prune: true
  destination:
    server: https://kubernetes.default.svc
    namespace: deploy-test
Enter fullscreen mode Exit fullscreen mode

The ArgoCD will sync with the repository that you set on spec.source part.

After all setup, finally, apply it.

kubectl apply -f k8s/deploy-test/argocd/deploy.yaml
Enter fullscreen mode Exit fullscreen mode

That's it!!! you are all done setting CI/CD!

4. Recap

You created an application source code repository and manifest repository.
In the source code repo, we added CI to build and push the docker image to ECR, update the manifest file, and create PR.

You can manage your deployment whether you merge PR. After merging PR, ArgoCD will sync with your newest code.
The pod will replace to newest image from ECR!

Thank you for reading my article, Happy Coding!

Reference:

Guide To GitOps

What is GitOps? | GitLab

Top comments (0)