ArgoCD Tutorial for Beginners: GitOps CD for Kubernetes #1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
What is GitOps, and what role argocd plays in it? Keeping configurations separate from the source code of an application and managing them through version control has been a best practice for a long time. When I refer to configuration, I am talking about various settings that an application requires to function correctly. These settings could be as simple as a database URL. Suppose an application is operating in a staging environment. In that case, it would store its data in a separate staging database. The emergence of public clouds and the increased adoption of automation has made it feasible to keep infrastructure configurations in Git repositories, using tools like Terraform. With these tools, it is possible to declaratively define the desired number of virtual machines to create, and then apply the Terraform configuration files, which automates the process of provisioning those new virtual machines. As people started relying more on Git for configuration management, it became the primary source of truth for infrastructure. This shift in thinking gave rise to new patterns and approaches to managing infrastructure changes. Instead of directly modifying Terraform configurations locally, the new approach involves creating a pull request for any changes to the infrastructure. After the team reviews and approves the request, the changes get merged, and the corresponding CI/CD pipeline detects the update and applies it automatically. The entire operations process revolves around making changes in Git and letting automation handle the rest. Argo CD is a continuous delivery tool designed to ensure that the Git state remains synchronized with the Kubernetes state. To deploy a new application using Argo CD, you start by creating a pull request, just like with infrastructure changes. Once the pull request gets approved and merged, Argo CD automatically applies the changes to the Kubernetes cluster. This process is relatively simple, with Argo CD primarily cloning the Git repository and running kubectl apply at regular intervals. Most of the automation in this process is built into Kubernetes and not Argo CD itself. Suppose you're using ArgoCD to manage your application's deployments. In that case, the typical workflow would begin with your build agent, like Jenkins, pulling the application's source code, running tests, and building a Docker image. If this process happens repeatedly for every change or commit to the application code, it's known as continuous integration. After building the Docker image, the build agent would then release the image to a Docker registry like Docker Hub or any other repository in use. Next, Jenkins would pull the GitOps repository containing infrastructure configurations and update the image tag to the newly built image. It would then commit this change back to the remote repository. On the other end of this pipeline, we have Argo CD, which monitors the GitOps repository for changes, such as a new Docker image. When Argo CD detects a change, it clones the repository and applies the updates, usually with kubectl apply or helm upgrade, depending on what you're using. This process is referred to as continuous delivery, where every change made to the application's source code results in a deployment in some environment. Typically, continuous delivery is used in development and staging environments. However, in production, manual approval is still necessary before deploying any changes. First of all, we need a Kubernetes cluster. If you already have it, you can skip this part, but I would highly recommend following along if you're a beginner. The easiest way to bootstrap Kubernetes is to use Minikube. It allows you to create local Kubernetes clusters in seconds. Recently they added support for docker; now, you don't need to install a virtual box or any other hypervisors. They also have very friendly documentation that you can follow along to install minikube. You can select any supported operating system and architecture and get installation instructions. I use a mac with a new apple silicon which is based on arm64 processor. Homebrew is a package manager for mac. You can copy command and run it in the terminal. It'll take a couple of minutes to install. I already have it installed. As I previously mentioned, the best way to bootstrap Kubernetes is to use a docker driver. You need to install docker as well and make sure that it's running. On a mac, you can simply search for docker and start desktop docker application. Wait a few seconds and run docker again. To start minikube, you can simply run miniukube start. But I would suggest using the latest supported version. In my case, it's 1.26.1. If you run minikube without this flag, it'll tell you what is the latest supported version. Also, I don't like to rely on defaults since they tend to change from version to version. I want to explicitly specify the docker driver. This will allow minikube to create Kubernetes nodes as docker containers. If you choose VirtualBox, for example, minikube will spin up virtual machines as Kubernetes nodes. When you run it, it will pull docker images and bootstrap the Kubernetes cluster locally and also configure kubectl to talk to your new cluster. Make sure to install kubectl before you run this command. To verify that you can access the Kubernetes cluster, you can get Kubernetes nodes. We have a single Kubernetes node which is also a control plane. Usually, you cannot run pods on the master, but minikube removes the taints from the control plane, which prevents the scheduling of new pods. Nowadays, the most common way to install open-source projects to your Kubernetes clusters is to use provided helm charts. If you don't want to use helm, you can run helm template command to generate yaml and apply it in that way. Let's go ahead and add argocd helm chart. Every time you add a new repo, you want to update the index. Let's search for the argocd chart. We're going to use the latest version at the moment, which is 3.35.4. I would recommend that you use the same version and upgrade only after you go through this tutorial. Most of the time, we want to override at least a few default variables. To get defaults, you can run helm show values and specify the path to save this file. If you open it, there are a lot of stuff you can find that you want to override. For example, argocd version, affinity, etc. Now, I'll show you how to install helm directly and with terraform. I like terraform because it allows you to declaratively define in the code what you want to install and where. And it's easier than you may think to actually use terraform. Create terraform folder. First of all, we need to define a provider. It allows you to authenticate with Kubernetes and provides resources that you can use to install helm charts. If you used minikube, you just need to point to the default kube config. In case you used terraform to create EKS cluster, for example, you can dynamically obtain a token to authenticate with a cluster. In the next file, we'll install argocd helm chart. In case you don't want to use terraform, you can just copy this command and run it in the terminal. But wait until we create values.yaml file to override some variables. To install the helm chart, we use helm_release terraform resource. The second argument is an arbitrary variable, which you can call wherever you want, but you should follow the same snake case naming conventions. The name property is a helm release name. I typically give it the same name as an application that I'm deploying. Next, you need to point to the helm chart repository. Then the chart name. In the case of terraform, you don't need to prepend the repo name, just a chart name. Then, specify the namespace. You should use default argocd namespace for the deployment. Otherwise, you would need to override some variables in the chart. Then create a namespace if it does not exist. Version of the helm chart. Use the same one for now. There are a couple of ways to override variables. One way is to use set statements and to target individual values. Or just create values.yaml file and specify the path to that file, which is my preferred method. When you need to set up tolerations and affinity, it's a lot of set statements, and it becomes not readable. Now let's create a values.yaml file. First of all, I want to use the latest argocd versions. Then I don't want argocd to generate a self-cert and redirect http to https. That's not how most people secure their endpoints. If you want to expose argocd outside, you would use ingress and terminate https on the ingress level and then route plain http to argocd. We'll set up the ingress later on in the tutorial. Before you can apply terraform, you need to initialize it. It will download all providers and initialize a local state. Then to deploy the helm chart, run terraform apply. It will take a couple of minutes to install it. In case it takes longer, you can open another terminal window and run helm status argocd in argocd namespace. It will give you an error. Or you can install argocd helm chart without terraform but make sure to clean up first. Try to get failed charts with helm list --pending -A. Now let's verify that argocd is successfully installed. Run kubectl get pods and make sure that all pods are in a running state and not in a crash loop or pending. By default, this helm chart will generate an admin password and store it in the Kubernetes secret, which is called initial admin secret and is used only once during the starup. You can change it if you wish. To get the password let's get that secret in yaml format. It will be encoded in base64. To deccode the secret, you can use echo and pipe it to the base64 utility. The percent sign indicates the end of the string; don't copy it. Now to access argocd, we can use port-forward command. The username is admin, and the password is the content of the Kubernetes secret. Now we're ready to create our first CD pipeline using GitOps. In this part, we'll create a public github repo and docker images just because it's easier to get started. Later we'll use private repos and images. First of all, let's go ahead and create a public github repository. You can call it whatever you want. It will contain the Kubernetes yaml files and helm charts. By modifying the content of this git repo, we'll operate our Kubernetes cluster; that's why it's called GitOps. It should be a public repo, and also add a readme file that we can clone it. Now let's copy the clone command. You can put it in any place on your workstation. I typically use devel folder. We'll come back to it later. We also need a docker hub account to store our docker images. If you already have it, you can just sign in, or you can register for free. A free docker hub account only allows you to store a single private image and an unlimited number of public images; that's what we actually want for now. You can create a repository first, but it's optional and only required when you create a private image. To push a new public docker image, we don't need to have a repo first. To simulate a CD pipeline, we need some kind of image to play with. Let's search and pull nginx public docker image. I'll use the latest version from the mainline, which is 1.23.3. You can use any of them; it does not matter. To push images later, we need to authenticate with the docker hub. Now let's pull the image. So far, we have a single image. To simulate CD pipeline, we would need to increment image tags to deploy new versions. Let's tag this open-source image using your personal username from the docker hub account. In my case, it's aputra. Now we have two images with exactly the same image id, meaning they are identical. Use the docker push command to upload that image to your personal docker hub account by using your own username. You can verify that you have uploaded your image by going to my profile. As you can see, I have a single docker tag for now, v0.1.0. Alright, next, we need to create Kubernetes deployment for that new docker image. Let's go ahead and open public github repo that we created earlier. Create a my-app folder. Now it is possible to manage namespaces and metadata such as namespace labels and annotations using argocd itself, but I prefer to create namespaces explicitly. Let's call it prod. Next is deployment. As I said, it's going to be based on the open-source nginx image that we uploaded to the docker hub. Replace this image with yours. The rest of it does not really matter for now. Since we are using GitOps, we need to add this change to the git tree and commit the change with some meaningful message describing your action. In this case, I want to deploy a new app to Kubernetes. It will show up in the argocd UI. And finally, push the changes to the remote github repo. Alright, we have the namespace and a deployment. But it's not all; we need to tell argocd to watch this particular github repo and my-app path. Let's go back to the original workspace where we deployed argocd. Create the first example folder. There are multiple ways to deploy apps; we'll start with the basics and create application custom resource first. This Application kind is provided by the CRDs that are created along with the rest of the deployments in the helm chart. Give it a name for the application, for example, my-app. Next is a namespace to create an Application object, don't confuse it with a target namespace for deployment. It is always must be the same argocd. Then the spec. Let's use a default project. Projects provide a logical grouping of applications, which is useful when Argo CD is used by multiple teams. Then the source of the deployment objects. We need to point it to our public github repo that we created. At this time, you can use https or ssh; it does not matter since it's a public repo, but I would recommend using .git suffix since argocd will not follow redirects. TargetRevesion. The head points to your main branch's latest commit. You can use git branches, git tags, or even primitive regex expressions. In the case of the helm, it should point to the chart version. We'll touch on it later. Then the path to track inside your github repo; if you followed along, you should have exactly the same path. The destination is useful when you use a single argocd instance to deploy applications to multiple clusters. In our case, we'll deploy the app to the same Kubernetes cluster where argocd is running, so we need to provide a path to the local Kubernetes api server. That's the bare minimum that we need to get started with argocd. We'll add more later. Now to instruct argocd to track our repo, we need to manually, for now, apply that application. You can try to find the nginx in Kubernetes, but it looks like it's not deployed yet. Alright, we have the new my-app argocd application, but it has a warning that it's out of sync. By default, argocd will automatically refresh and compare the state of Kubernetes and git, but it will not apply it. This is a default strategy for argocd and can be useful in production environments if you want to be very careful. I don't like manual steps, so I tend to configure it to automatically sync up the git state with Kubernetes. To actually deploy the app, we need to click sync. For now, keep all defaults and synchronize. Now it's working, and argocd is deploying nginx to Kubernetes. Here we have the argocd application resource, then the namespace, deployment object, and replication set, which is managed by deployment, and finally, the pod itself. And if you go back to the terminal, you'll find a pod running in the prod namespace. Also, you'll notice that application is now green, which means the git state matches the Kubernetes state. When I say the state, I mean all different Kubernetes deployments and their configurations, including replica count, selectors, and others. Now let's simulate the CI/CD pipeline and release a new version of our app. It will be using exactly the same default nginx image, but this time we'll increment a tag to simulate the application upgrade. And don't forget to push that image to the docker hub. Let's bring back the public GitHub repo that we use for GitOps. And for now, manually increment the image tag to v0.1.1. Commit the changes and push them to the remote repo. Later we'll automate these steps in the script that can be used by the build agents such as Jenkins, github actions, etc. Without setting up a webhook, it may take up to 5 minutes for argocd to detect the change in the git. In the case of GitHub, if you reduce the time for a refresh, for example, to 1 minute or less, Github has a rate limit and simply start rejecting argocd api calls. Also, to set up a webhook means you need to expose argocd to the internet, which is not feasible for most companies. If you host your own git, such as GitLabs, inside your environment, you can totally set up a webhook and speed up the process. Now we can wait or just click refresh. Argocd detected the drift and now shows that it's out of sync. With the default strategy, we're forced to synchronize the state manually every time. Let's click sync. Argocd reapplied the deployment and released a new version to our Kubernetes cluster. Next, let's remove that manual step that requires us to synchronize every time manually. Go back to the application resource and add syncPolicy. It's going to be automated. Set prune to true, enable self heal. It would allow synchronizing state if the Kubernetes state was changed for some reason, maybe a manual change on the cluster by someone. Allow empty disables deleting all application resources during automatic syncing. You can adjust those settings based on your needs. Then we can modify sync behavior. Enables kubernetes validation; for example, if the deployment has an unknown field, it will be rejected by argocd, which is actually the default. Create a namespace if it does not exist, similar to helm charts. I like to create namespaces explicitly. Propagation. And prune last. Apply the changes to the application resource. To verify that automatic sync works, let's release a new version, 1.2. And push it to the docker hub. Make the appropriate change in the GitOps repo by incrementing the git tag. Add the commit and push it as well to GitHub. This time I'll wait 5 minutes for argocd to refresh the git state. You can wait or click refresh. Now argocd automatically release a new version to the Kubentes. You can check it in the terminal as well. The typical workflow for CI/CD pipeline when using argocd is the following. The build agent builds the app, releases a new docker image, and modifies the GitOps repo by setting the image to the one it just created. I want to show you how to create a simple script that you can use on the build agent. Let's call it upgrade.sh. Exit on the first error that happens during the execution of this script. Get the version tag from the script's first argument. Usually, it matches the git tag. Print out the current version to the console. Release a new version of the docker image using that new tag. Upload the docker image to the remote repository. Create a temporary folder where we are going to clone the GitOps repo. Clone the repo. There are multiple ways to modify the content of the deployments; the simplest one and most reliable is to use the built-in to Unix systems sed tool. It traverses the file and replaces the tag. You'll need to replace it with your docker hub username. Later I'll show you other strategies. Finally, commit the changes to the GitOps repo and push it back to GitHub. Also, you want to delete that temporary folder that we created for the GitOps repo. Make this script executable. Let's see what we have right now in the GitOps repo. The current version is 1.2. To execute the script, we need to provide a new tag, for example, 1.3. Okay, let's run it. It will release a new version and commit the new tag to the GitOps repo. In GitHub, you can see that the script committed a new version, 1.3. Go back to the argocd UI and check the current version. Now let's refresh, and argocd will deploy a new 1.3 nginx version to Kubernetes. In the terminal, you can also verify that the new app was deployed. Now let me show you what happens when you delete the application resource. It looks like argocd removed it from the UI, but the app is still running. I actually want to delete the Kubernetes app as well when I delete the application resource. To do that, we need to add a finalizer to the metadata. It will force argocd to delete the Kubernetes app first and then removes it from the argocd state. Let's create that application again in argocd. Okay, it's here. Now let's remove it and see what happens. Alright, it works as expected; it removed it from Kubernetes and then from argocd, which is what we want most of the time. Especially helpful when we use App Of Apps Pattern that we'll talk about later. When you have a lot of applications that you want to deploy to Kubernetes, you don't want to create them manually. The most common approach that people use is the app of apps pattern when you manage the creation and deletion of the apps using the same GitOps repository. Let's go back to the GitOps repo and update the namespace to foo for the first application. Next, clone this application and rename it to the second-app. For this app, let's use bar namespace. You could, of course, to deploy them to the same namespace as well and call it staging. Now, let's create a folder structure that will allow us to manage multiple apps and environments from a single repository. Create a new staging folder and move those apps to that environment. To deploy these applications automatically to Kubernetes, create another folder called apps under staging. In this folder, we'll put all the applications that we want to deploy in a staging environment. Here we'll create application resources; before, we had to apply them manually using kubectl one by one, and now argocd will manage them for us. This application will manage my-app, and don't forget to include finalizers if you want to delete all of them using argocd as well. And create another application resource for the second app. It's identical to the previous except it uses a different path. As you can see, we have Kubernetes deployment files and corresponding argocd application resources to register them with argocd. The workflow for helm and kustomize is similar, except that you target helm charts. As always we need to add all those changes to the git, commit and push to the GitHub. You can visit GitHub to verify that you have uploaded all the required files. Now let's switch back to the main project with examples. Let's go ahead and create a second example. This application resource will target apps path and apply all of them on our behalf. If you open argocd right now, you should not have any apps. Let's go ahead and apply the second example. Immediately you can notice that argocd will create apps-staging first and then deploy my-app and the second app. You can verify that pods in the foo and bar namespace are running. It's also much easier to delete all those apps. Now you just need to delete the main apps application. Argocd will remove all child apps and then apps-staging itself. Since we have finalizers on each app, argocd also removed them from Kubetnes. You can use this pattern for helm charts and kustomize deployments. In the following section, I'll show you how to use private git repositories and private docker images. Let's create a private docker repository. Let's call it nginx-private. It's going to be a slightly different approach in AWS which I'll go through at the end of this tutorial. On the other hand, managing private git repositories will be the same among all clouds. Let's create a private github repo and call it lesson-158-private. First of all, we need to release a new docker image and push it to the private repository. The push process is exactly the same as with public images. Next, we need to clone the private github repo. Now you could follow the same folder structure with environments, but for simplicity, I'll keep it flat. Create a my-app folder. Then the namespace. And a deployment object. Here we need to use our private docker image. That's all for now for this GitOps repo. Let's commit the changes and push them to GitHub. Create a new example 3 folder. And define the application resource. The main difference here is that we are not using https anymore and also specifying a new private github repo. The rest of the application is the same. Let's try it out; apply the application. Let's see in the argocd ui if the application was deployed. It looks like we have an issue. It's maybe not obvious from this message, but it means argocd does not have access to the private github repository. Before we try to fix it, let's delete this application. There are multiple ways to authenticate with private repositories, including personal tokens and github apps. But the best option for CI/CD pipelines, in my view, is to use ssh keys. Let's go ahead and generate an ssh key for the argocd. We'll use an elliptic curve. On older machines and maybe windows, you still need to use the rsa keys by using the following command. Step number one is to upload the public ssh key to the github repository. Go to settings and deploy the new key. This key will grant access to only a single repository. Call it argocd and paste the key. Now for the typical argocd workflow, we need only read access. Later when we start using the image updater, we'll update it to read and write permissions. The next step is to create a secret with the private key in kubernetes and link it with that private repository. You must specify the same github repo that you use in the application resource. Now let's copy the private key and paste it into the secret. To test it, you need to create that secret first; otherwise, you may have the same error, but if you refresh argocd, it will go away. Then apply the same application. This time it looks like argocd was able to pull the github repo, but we have an issue with the pod. It's not running, and we have errors. I think you already know why. Let's get the pods in the foo namespace. And we have an error imagePullBackOff. If you describe that pod, you'll find that minikube does not have permission to pull that image. It's not directly related to argocd, but we still need to fix it. To grant access to minukube, we can either use a personal password, but the better approach is to generate a read-only token. Go to security in the docker hub and generate the token. The last step is to create Kubernetes secret of type docker-registry in the same namespace where you deploy your application. I know it's not convenient to create the same secret in all namespaces where you deploy your private image. Later in AWS, I'll show you another way. Use your token to create a secret. In the GitOps repo, we also need to explicitly provide that secret to the deployment object. Just add the imagePullSecrets section with the name of the secret. Commit the changes and push. Now in Argocd, you can wait a few minutes or click refresh. Now it seems to work. Let's also verify in Kubernetes that the pod is running. Alright, that's all for this section; let's clean up and delete the deployment. In the next section, I want to show you how to deploy Helm charts. As an example, we'll take metrics-server open-source helm chart. Typically it is used for horizontal pod autoscaling and also can be used directly by running the kubectl top command. As with any Helm charts, you need to know what kind of variables you can override. The easiest and most accurate way is to use helm show command with a specific version. Let's go ahead and add metrics-server helm repo. Search for the metrics server. Keep a note of the current version. Next, save the default values. You can open that file and find all possible variables that you may wish to override. Now we're ready for the deployment. Create a new example 4 folder with an application resource. Specify the same helm repo under repoURL and provide the helm version that you want to install. Chart name. Then you have the option to select a version of the helm; most of the time, you want to use the latest v3 version. Release name for the chart. Since this chart is open to anyone, we don't need to provide any credentials to access it. Now there are a few ways that you can use to override default variables. One of them is to target individual values. Which is fine when you need to override something like an image name but with tolerations and affinity, it becomes a mess. The following approach that I like more is to provide a yaml with all variables that you want to override. When using minikube, you need to pass insecure-tls flag to the metrics server. Otherwise, it won't work. Then the same destination section. By default, argocd will deploy this helm chart o the argocd namespace. If you want to customize the namespace, you can provide it here. You can find an example in my github repo. And the same sync policy. Now if you provide a custom namespace under destination and it does not exist, you need to flip create namespace to true. That's pretty much all; let's go ahead and apply this example. In the UI, you can see a bunch of resources created by the hem chart. To verify that metrics-server is running, let's get the CPU and memory usage of the pods in the kube-system namespace. You can also get the usage of the nodes. That's all for this example. Let's uninstall that chart. Another common and my prefered approch to customize deployment objects is to use kustomize. It helps to keep your code dry and only override parts of the application that are specific to that environment. Let's create my-app-base folder. Create a namespace. It can be anything; we will override it in each environment. Then the deployment object. We don't need to specify a tag or a namespace since it will be populated by kustomize. And you also need to include kustomize.yaml file in the base folder, and specify what files can be customized. I have a full tutorial on how to use kustomize that you can watch after we finish this example. Next, let's create the same folder structure to hold different environments. I created a dev environment by I meant to create staging. And then my-app folder. Here we only need to create a single kustomize file and override some properties. Let's change the default namespace to staging. Then the image. This image tag will be updated from the build agent, such as Jenkins, with a single kustomize command. And you also must specify the path to the base folder that contains my-app. Let's commit all the changes and push them to the remote. Now create a new example 5 folder with the application resource. You can totally use the same app of apps pattern and commit all those applications to the gitops repo. Since I deleted the previous secret to access private repo, I need to copy it here from the previous example. From the application perspective, it looks similar to how we deploy plain yaml files. You could kustomize some of the parts from here, but I recommend using the gitops repo. Let's apply it. It created all the resources, including staging namespace and deployment. Verify in the terminal that the pod is running. Looks good; let's tear it down. In the next video, we'll explore a slightly different approach to continuous delivery using Argocd. In the following video, we'll use an image updater to decouple continuous integration from continuous delivery.
Info
Channel: Anton Putra
Views: 33,994
Rating: undefined out of 5
Keywords: argocd, argo cd, argocd tutorial, argocd tutorial for beginners, argocd tutorial helm, argocd tutorial minikube, argocd full tutorial, argocd helm, argocd demo, argocd kustomize, argocd app of apps, argocd rollout, argocd kubernetes, argocd kubernetes helm, argocd kubernetes secrets, argocd gitops, argocd gitops helm, argocd gitops tutorial, gitops tutorial, anton putra, devops, kubernetes, k8s, k8s tutorial, sre, cloud, kubernetes ci cd pipeline, kubernetes argocd, fluxcd
Id: zGndgdGa1Tc
Channel Id: undefined
Length: 38min 50sec (2330 seconds)
Published: Sun Mar 26 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.