Malak Younes
Malak Younes 2024-05-02

Kubernetes CD: Embracing the GitOps Workflow with ArgoCD

Kubernetes CD: Embracing the GitOps Workflow with ArgoCD

What is GitOps?

GitOps is a modern infrastructure management strategy that uses Git as a single source of truth for declarative infrastructure and applications. By using Git at the core of the deployment pipelines, all changes are comprehensible and can be audited, making it easy to track every alteration to the environment. In GitOps, Git repositories hold configurations, infrastructure definitions, and manifests, which automated systems then continuously synchronize with the live systems to ensure correctness and desired state enforcement.

When paired with Kubernetes, GitOps amplifies the strengths of both technologies, creating a reciprocal impact on the management of containerized environments. Particularly, it is most effective when the pull-based model is implemented. In this model, an agent within the Kubernetes cluster continuously monitors the git repository for any changes to the configuration files instead of a push model where changes trigger a pipeline.

One of the most popular CD tools that leverage the GitOps pull-based model is ArgoCD. Although other tools that are just as popular exist, like Flux, we will focus on ArgoCD.

 

CI/CD GitOps Workflow with Kubernetes

articlesimagesimage1.jpeg

It is advisable to maintain the repository containing the infrastructure-as-code (such as Kubernetes manifests) separately from the application source code repositories. This approach effectively separates the Continuous Integration (CI) from the Continuous Deployment (CD) processes. The CI process occurs through a pipeline that is triggered by a pushed commit. Meanwhile, the CD process is handled by ArgoCD, which monitors the manifests for any changes and then deploys them into the cluster.

 

How ArgoCD Operates

ArgoCD is an open-source GitOps continuous delivery tool. It monitors the cluster and the declaratively defined Kubernetes manifests stored in the Git repository and resolves the differences between them, effectively automating application deployment. The Git repository represents the desired sync state, whereas the cluster represents the current state. Any changes detected in the Git repository will prompt ArgoCD to sync the cluster's state with the desired state.

 

Installing ArgoCD and Setting up the Repository

You can install argocd using helm or by applying the manifest like so:

kubectl create namespace argocd
kubectl apply -n argocd -f <https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml>

The first step you need to do following the installation is to authorize ArgoCD with your private git repository. The git repository will contain the desired application state as Kubernetes manifests. It is good to note that ArgoCD supports different Kubernetes configuration and management tools like Kustomize and Helm.

apiVersion: v1
kind: Secret
metadata:
  name: private-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
data:
  url: <base64-encoded-url>
  password: <base64-encoded-password>
  username: <base64-encoded-username>

ArgoCD needs to authorize the Git repository using a secret. This secret should have the above label for ArgoCD to be able to identify and use it.

 

ArgoCD Application

An Application in ArgoCD is a custom resource tasked with managing the deployment of your infrastructure manifests to the designated Kubernetes cluster. ArgoCD can deploy Kubernetes manifests through Kustomize or Helm charts, along with compatibility for various other tools. For our demonstration, we will utilize Kustomize. Simply indicate the path to your kustomization.yml file to proceed. We'll provide an example to help you understand the concept better, so make sure to follow along!

 

Infrastructure Manifest Folder Structure

Assume that we define the infrastructure as code for an application called cache-connector-eth-mainnet, which has different configurations based on its deployed environment. We utilize the Kustomize tool to leverage the base and overlays concept, which can be found here. The manifest files are located in the git repository we've set up earlier. It looks something like this:

├── base
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── image_pull_secret.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── environments
    ├── prd
    ├── stg
    └── tst
        └── eth
            ├── mainnet
            │   ├── external-secrets.yaml
            │   ├── kustomization.yaml
            │   └── namespace.yaml
            └── sepolia
                ├── external-secrets.yaml
                ├── kustomization.yaml
                └── namespace.yaml

 

Deploying the Application

Now, we define an ArgoCD application called cache-connector-eth-mainnet-app like so:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cache-connector-eth-mainnet-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: cache-connector-eth-mainnet
    server: '<https://kubernetes.default.svc>'
  source:
    path: cache/connector/environments/tst/eth/mainnet
    repoURL: '<https://gitlab.com/.../kubernetes.git>'
    targetRevision: HEAD
  project: default # logical grouping in case multiple teams exist
  syncPolicy:
    automated:
      prune: true # automatically deletes resources that are no longer in its state file
      selfHeal: true # changes applied to live cluster (that deviates from the state defined in git) triggers automatic sync
      allowEmpty: false # doesn't allow empty applications
  syncOptions:
    - CreateNamespace=true
    - PrunePropagationPolicy=foreground
    - PruneLast=true
    - Validate=true

 

ApiVersion and Kind

  • apiVersion: argoproj.io/v1alpha1: This indicates the version of the ArgoCD API that the configuration is using.
  • kind: Application: This specifies that we’re defining an ArgoCD Application.

 

Metadata

  • name: This is the name of the ArgoCD application.
  • namespace: argocd: The namespace in which the ArgoCD application is defined.
  • finalizers: Finalizers help regulate the deletion of Kubernetes resources. Here, resources-finalizer.argocd.argoproj.io ensures that any associated resources with this application are correctly handled (cleaned up) when the application itself is deleted.

 

Spec

  • destination:
    • namespace: cache-connector-eth-mainnet: This indicates the namespace in which the Kubernetes resources (defined by this ArgoCD application) should be deployed.
    • server: '<https://kubernetes.default.svc>': This is the Kubernetes API server’s address where the resources will be deployed.
  • source:
    • path: Path within the Git repository that holds the Kubernetes manifests (where it monitors for changes in the Kubernetes manifests).
    • repoURL: The Git repository URL containing the Kubernetes manifests (make sure ArgoCD is authenticated with the repo).
    • targetRevision: HEAD: This indicates the desired state of the application will be taken from the latest commit (HEAD) of the branch (default is master/main branch).
  • project: default: Logical grouping within ArgoCD. If there are multiple teams using ArgoCD, they can have separate projects for isolation and RBAC purposes.
  • syncPolicy: Describes the synchronization policy between the desired state in Git and the live state in the cluster.
    • automated: This section dictates automatic synchronization behavior.
      • prune: true: If a resource is present in the live state but not in the desired state (in Git), it will be deleted automatically.
      • selfHeal: true: If changes are detected in the live state that deviates from the desired state in Git, ArgoCD will automatically synchronize to match the desired state.
      • allowEmpty: false: This ensures the application has resources and won’t allow an application that doesn’t have any resources.
    • syncOptions: Provides additional synchronization options.
      • CreateNamespace=true: If the target namespace doesn’t exist, ArgoCD will create it.
      • PrunePropagationPolicy=foreground: Defines how Kubernetes should handle the deletion of resources. Using foreground ensures that the main resource is deleted last after its dependents.
      • PruneLast=true: Ensures certain resources are pruned last.
      • Validate=true: Enables validation of resources during synchronization.

Applying this yaml will result in ArgoCD searching for the Kustomization file and applying it. Moreover, any changes to the infrastructure of the cache-connector-eth-mainnet you commit to the git repository will result in ArgoCD noticing a difference between the current state in the cluster and the desired state in the git repository. This means that editing the infrastructure will be restricted to the git repository, because any changes done directly using the Kubernetes API will result in ArgoCD overriding them in the next state sync.

 

ArgoCD App of Apps Approach

Deploying an application manually by applying the ArgoCD application manifest to the cluster is not the best approach for several reasons. One reason is that the user applying would require access to the Kubernetes cluster. Another reason is that the application deployment and deletion management would become a hassle in case of a large number of applications. This is where ArgoCD app of apps approach comes in handy. Similar to how we deploy an ArgoCD application that monitors a specific Kubernetes manifest directory, thus managing and automatically syncing the resources in that directory, we can deploy an ArgoCD application that monitors the folder ArgoCD apps where all ArgoCD apps reside. The root app will automate the deployment of applications by simply adding the Kubernetes manifests of an application along with an ArgoCD application that points to the manifests directory. Once the application changes are committed, the root ArgoCD application detects a change in the directory and applies the newly added ArgoCD application. The latter deployed ArgoCD application detects that the manifest files located in the folder its monitoring are not deployed, and then applies the manifests in the cluster.

├── argocd_apps
│   ├── cache-connector-eth-mainnet-app.yaml
│   ├── cache-connector-eth-sepolia-app.yaml
└── root_app
    └── argocd_apps_deployment.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: tst-argocd-root-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: admin
    server: <https://kubernetes.default.svc>
  source:
    path: ../argocd_apps
    repoURL: <https://gitlab.com/.../kubernetes.git>
    targetRevision: HEAD
  project: default
  syncPolicy:
    automated:
      prune: true # automatically deletes resources that are no longer in its state file
      selfHeal: true # changes applied to live cluster (that deviates from the state defined in git) triggers automatic sync
      allowEmpty: false # doesn't allow empty applications
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
      - Validate=true

Bassically instead of applying each argocd application, we only apply the argocd root application ( the app of apps ) like so.

kubectl apply -f $env_argocd_apps_deployment.yaml

 

Conclusion

The pull-based approach in GitOps, exemplified by tools like ArgoCD, represents a significant advancement in how deployments and infrastructure management are handled in cloud-native environments. This method not only enhances security by reducing external access to Kubernetes clusters but also improves resilience by ensuring continuous alignment with the desired state. As organizations look to build more dynamic, scalable, and secure systems, embracing a pull-based GitOps workflow with ArgoCD offers a compelling pathway to achieving these goals.

Share

More to read