Deploy to Kubernetes with ArgoCD and CircleCI
Software Engineer
GitOps modernizes software management by letting developers manage infrastructure and code through a Git repository as a single source of truth. To make GitOps work, a container orchestration system like Kubernetes helps manage complex infrastructure.
ArgoCD, a Kubernetes-native continuous deployment (CD) tool, streamlines GitOps by pulling code changes from Git and deploying them directly to Kubernetes resources. This tutorial will show you how to deploy a Node.js application on Azure Kubernetes Service (AKS), using CI/CD to build and push an image to Docker Hub and ArgoCD to deploy to Kubernetes.
Prerequisites
To follow along with this tutorial, you will need a few things first.
Accounts for:
These tools installed on your system:
After you have all the pre-requisites complete you are ready to go to the next section.
Cloning the Node.js application
The main focus of this tutorial deploying an application on Kubernetes. You can directly clone the sample Node.js application to your GitHub and continue with the rest of the process.
To clone the project, run:
git clone https://github.com/CIRCLECI-GWP/aks-nodejs-argocd.git
The Node.js application lives in the app.js
file and contains:
const express = require("express");
const path = require("path");
const morgan = require("morgan");
const bodyParser = require("body-parser");
/* eslint-disable no-console */
const port = process.env.PORT || 1337;
const app = express();
app.use(morgan("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: "true" }));
app.use(bodyParser.json({ type: "application/vnd.api+json" }));
app.use(express.static(path.join(__dirname, "./")));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "./index.html"));
});
app.listen(port, (err) => {
if (err) {
console.log(err);
} else {
console.log(`App at: http://localhost:${port}`);
}
});
module.exports = app;
The application will be running on port 1337
for this tutorial.
You can run the application locally by first installing the dependencies. In the project’s root, type:
npm install
Then run the application with the command:
node app.js
The application should now be up and running at the address http://localhost:1337
.
Containerizing the Node.js application
To deploy the application of Kubernetes you first need to containerize it. To containerize applications using Docker as the container runtime tool, you need to create a Dockerfile.
A Dockerfile is a text document that contains all the instructions Docker needs to assemble an image.
Create a new file in the root directory of the project and name it Dockerfile
. Copy the following content in the file:
# Set the base image to use for subsequent instructions
FROM node:alpine
# Set the working directory for any subsequent ADD, COPY, CMD, ENTRYPOINT,
# or RUN instructions that follow it in the Dockerfile
WORKDIR /usr/src/app
# Copy files or folders from source to the dest path in the image's filesystem.
COPY package.json /usr/src/app/
COPY . /usr/src/app/
# Execute any commands on top of the current image as a new layer and commit the results.
RUN npm install --production
# Define the network ports that this container will listen to at runtime.
EXPOSE 1337
# Configure the container to be run as an executable.
ENTRYPOINT ["npm", "start"]
If you have Docker installed, you can build and run the container locally for testing. Later on in this tutorial, you will learn how to automate this process with CircleCI orbs.
To build and tag the container, enter:
docker build -t aks-nodejs-argocd:latest .
Confirm that the image was successfully created by running this command from your terminal:
docker images
Then run the container using the command:
docker run -it -p 1337:1337 aks-nodejs-argocd:latest
The application should now be up and running at the address http://127.0.0.1:1337
. You can confirm that it is running by visiting localhost:1337
in your browser.
Commit and push the changes to your GitHub repository. If you’re pushing the sample project, you’ll first want to create a new, empty repository on Github and set the remote URL to this new repository. You can set the remote URL with:
git remote set-url origin <new-repo-url>
Configuring Kubernetes manifests for deployment
To deploy containers on Kubernetes, you will have to configure Kubernetes with all the settings required to run your application. Kubernetes uses YAML files for configuration.
Create a directory named manifests
in the root directory of the project. Then create these files in the newly created folder:
namespace.yaml
deployment.yaml
service.yaml
kustomization.yaml
In Kubernetes, namespaces provide a mechanism for isolating groups of resources within a single cluster. In namespace.yaml
, paste this config:
apiVersion: v1
kind: Namespace
metadata:
name: nodejs
labels:
name: nodejs
This file creates a namespace named nodejs
inside the Kubernetes cluster. All the resources would be created in this namespace.
Kubernetes Deployments manage stateless services running on your cluster. Their purpose is to keep a set of identical pods running and upgrade them in a controlled way – performing a rolling update by default to get each pod to the desired state.
The contents of the deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs
namespace: nodejs
labels:
app: nodejs
spec:
replicas: 3
selector:
matchLabels:
app: nodejs
template:
metadata:
labels:
app: nodejs
spec:
nodeSelector:
"beta.kubernetes.io/os": linux
containers:
- name: aks-nodejs-argocd
image: aks-nodejs-argocd
ports:
- name: http
containerPort: 1337
The key takeaway from this code is the containerPort
. This is where the application will be running and where the container-image
will be pulled and deployed in the namespace on the Kubernetes cluster.
Kubernetes Service is an abstraction that defines a logical set of pods and a policy for accessing them. You need the Kubernetes Service type LoadBalancer
to make the deployment accessible to the outside world.
The contents of the service.yaml
should be:
apiVersion: v1
kind: Service
metadata:
name: nodejs
namespace: nodejs
labels:
app: nodejs
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 1337
selector:
app: nodejs
The key takeaway from this code are the targetPort
, port
and type
:
targetPort
is the container portport
is where the application will be runningtype
is the type of service
To deploy the latest version of the application on the Kubernetes cluster, the resources have to be customized to maintain the updated information. You can use Kustomize, which is a tool for customizing Kubernetes configurations.
The contents of the kustomization.yaml
are:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- namespace.yaml
namespace: nodejs
images:
- name: aks-nodejs-argocd
newName: aks-nodejs-argocd
newTag: v1
The key takeaways from this configuration are newName
and newTag
, which will be updated with the latest Docker image information as part of the continuous integration process.
Commit and push these files into the main
branch of the GitHub repository you had cloned earlier.
Launching the Azure Kubernetes Service (AKS) cluster
In this tutorial, you will be deploying the application on Azure Kubernetes Service (AKS) cluster. To create the AKS cluster, the Azure CLI should be connected to your Azure account.
To launch an AKS cluster using the Azure CLI, first create a Resource Group with this command:
az group create --name NodeRG --location eastus
Next, run the following to launch a two-node cluster:
az aks create --resource-group NodeRG --name NodeCluster --node-count 2 --enable-addons http_application_routing
Note: If you generated any SSH keys in your system previously, you need to add the optional --generate-ssh-keys
parameter to this command. This auto-generates SSH public and private key files if they are missing. The keys are stored in the ~/.ssh
directory.
The AKS cluster will take 10 to 15 minutes to launch.
Installing ArgoCD in the AKS Cluster
Once the cluster is up and running, you can install ArgoCD inside the cluster. You will use ArgoCD for deploying your application.
To install the application, use the Azure CLI. Configure kubectl
to connect to AKS using this command:
az aks get-credentials --resource-group NodeRG --name NodeCluster
To install ArgoCD in your cluster, use these commands:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
ArgoCD will be installed in the argocd
namespace. To get all the resources in the namespace enter:
kubectl get all --namespace argocd
Exposing the ArgoCD API server
By default, the ArgoCD API server is not exposed with an external IP. Because you will accessing the application from the internet during this tutorial, you need to expose the ArgoCD server with an external IP via Service Type Load Balancer.
Change the argocd-server service type to LoadBalancer:
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
Note: You can also use Kubectl port forwarding to connect to the API server without exposing the service. To do this instead, use this command: kubectl port-forward svc/argocd-server -n argocd 8080:443
This would let you access the API server using https://localhost:8080
.
Accessing the ArgoCD Web Portal
Once you have exposed the ArgoCD API server with an external IP, you can now access the portal with the external IP address you generated.
ArgoCD is installed in the argocd
namespace. Use this command to get all the resources in the namespace:
kubectl get all --namespace argocd
Copy the External-IP
corresponding to service/argocd-server
.
You can access the ArgoCD UI inside the cluster by visiting this external IP in your browser.
To log into the portal, you will need the username and password. The username is set as admin
by default.
To fetch the password, execute this command:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo
Use this username/password combination to log into the ArgoCD portal.
Configuring Kubernetes manifests for ArgoCD
To configure ArgoCD to deploy your application on Kubernetes, you will have to set up ArgoCD to connect your Git Repository and Kubernetes in a declarative way using YAML for configuration.
Apart from this method, you can also set up ArgoCD from the Web Portal or using the ArgoCD CLI. Because this tutorial is following GitOps principles, we are using the Git repository as the sole source of truth. Therefore the declarative method using YAML files works best.
One of the key features and capabilities of ArgoCD is to sync via manual or automatic policy for deployment of applications to a Kubernetes cluster.
To get started, create a directory named argocd
in the root directory of the project. Create a new file in the new directory and name it config.yaml
.
Manual Sync Policy
We will define a policy to manually synchronize your application by way of your CI/CD pipelines. Whenever a code change is made, the CI/CD pipeline is triggered, and calls the ArgoCD server APIs to start the sync process based on the changes you will commit. For communicating with the ArgoCD server APIs, you can use the ArgoCD CLI. You can also use one of the SDKs available for various programming languages.
For setting up the Manual Sync policy for ArgoCD, paste this in the config.yaml
:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: aks-nodejs-argocd
namespace: argocd
spec:
destination:
namespace: nodejs
server: "https://kubernetes.default.svc"
source:
path: manifests
repoURL: "https://github.com/Lucifergene/aks-nodejs-argocd"
targetRevision: circleci-project-setup
project: default
Automated Sync policy
ArgoCD has the ability to automatically sync an application when it detects differences between the desired manifests in Git, and the live state in the cluster.
A benefit of automatic sync is that CI/CD pipelines no longer need direct access to the ArgoCD API server to perform the deployment. Instead, the pipeline makes a commit and push to the Git repository with the changes to the manifests in the tracking Git repo.
If you want to set to the Automated Sync policy, you need to paste this in the config.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: aks-nodejs-argocd
namespace: argocd
spec:
destination:
namespace: nodejs
server: "https://kubernetes.default.svc"
source:
path: manifests
repoURL: "https://github.com/Lucifergene/aks-nodejs-argocd"
targetRevision: circleci-project-setup
project: default
syncPolicy:
automated:
prune: false
selfHeal: false
Commit and push these files into the main
branch of the GitHub repository you cloned earlier.
Creating the continuous integration pipeline
The objective of this tutorial is to show how you can deploy applications on Kubernetes through continuous integration (CI) using CircleCI and continuous deployment (CD) via ArgoCD. The CI pipeline should trigger the process of building the container, pushing it to Docker Hub, and the CD should deploy the application on Kubernetes.
To create the CI pipeline, you will be using CircleCI integrated with your GitHub account. Create a directory called circleci
and a file inside it called config.yml
.
The content of config.yml
should be:
version: 2.1
orbs:
docker: circleci/docker@2.1.1
azure-aks: circleci/azure-aks@0.3.0
kubernetes: circleci/kubernetes@1.3.0
jobs:
argocd-manual-sync:
docker:
- image: cimg/base:stable
parameters:
server:
description: |
Server IP of of ArgoCD
type: string
username:
description: |
Username for ArgoCD
type: string
password:
description: |
Password for ArgoCD
type: string
steps:
- run:
name: Install ArgoCD CLI
command: |
URL=https://<< parameters.server >>/download/argocd-linux-amd64
[ -w /usr/local/bin ] && SUDO="" || SUDO=sudo
$SUDO curl --insecure -sSL -o /usr/local/bin/argocd $URL
$SUDO chmod +x /usr/local/bin/argocd
- run:
name: ArgoCD CLI login
command: argocd login << parameters.server >> --insecure --username << parameters.username >> --password << parameters.password >>
- run:
name: Manual sync
command: argocd app sync $APP_NAME
- run:
name: Wait for application to reach a synced and healthy state
command: argocd app wait $APP_NAME
argocd-configure:
executor: azure-aks/default
parameters:
cluster-name:
description: |
Name of the AKS cluster
type: string
resource-group:
description: |
Resource group that the cluster is in
type: string
steps:
- checkout
- run:
name: Pull Updated code from repo
command: git pull origin $CIRCLE_BRANCH
- azure-aks/update-kubeconfig-with-credentials:
cluster-name: << parameters.cluster-name >>
install-kubectl: true
perform-login: true
resource-group: << parameters.resource-group >>
- kubernetes/create-or-update-resource:
resource-file-path: argocd/config.yaml
bump-docker-tag-kustomize:
docker:
- image: cimg/base:stable
steps:
- run:
name: Install kustomize
command: |
URL=https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v4.5.2/kustomize_v4.5.2_linux_amd64.tar.gz
curl -L $URL | tar zx
[ -w /usr/local/bin ] && SUDO="" || SUDO=sudo
$SUDO chmod +x ./kustomize
$SUDO mv ./kustomize /usr/local/bin
- checkout
- run:
name: Bump Docker Tag
command: |
cd manifests
kustomize edit set image $APP_NAME=$DOCKER_LOGIN/$APP_NAME:$CIRCLE_SHA1
- add_ssh_keys:
fingerprints:
- "$SSH_FINGERPRINT"
- run:
name: Commit & Push to GitHub
command: |
git config user.email "$GITHUB_EMAIL"
git config user.name "CircleCI User"
git checkout $CIRCLE_BRANCH
git add manifests/kustomization.yaml
git commit -am "Bumps docker tag [skip ci]"
git push origin $CIRCLE_BRANCH
workflows:
Deploy-App-on-AKS:
jobs:
- docker/publish:
image: $DOCKER_LOGIN/$APP_NAME
tag: $CIRCLE_SHA1,latest
- bump-docker-tag-kustomize:
requires:
- docker/publish
- argocd-configure:
cluster-name: $CLUSTER_NAME
resource-group: $RESOURCE_GROUP
requires:
- bump-docker-tag-kustomize
# Paste the following only when you opt for the ArgoCD manual-sync-policy:
- argocd-manual-sync:
server: $ARGOCD_SERVER
username: $ARGOCD_USERNAME
password: $ARGOCD_PASSWORD
requires:
- argocd-configure
The CI workflow consists of three jobs:
docker/publish
builds and pushes the container to Docker Hub.bump-docker-tag-kustomize
updates the Docker Image Tag and generates a consolidated Kubernetes configuration file.argocd-configure
applies the ArgoCD Configuration on the AKS cluster.argocd-manual-sync
is needed only when you will be opting for the manual sync policy. For automatic sync, you can omit this job from the file.
In this workflow, we have extensively used CircleCI orbs. Orbs are open-source, shareable packages of parametrizable, reusable configuration elements, including jobs, commands, and executors.
Commit and push these changes to your GitHub repository.
Setting up the project on CircleCI
The next step to deploying your application to AKS is connecting the application in your GitHub repository to CircleCI.
Go to your CircleCI dashboard and select the Projects tab on the left panel. Click the Set Up Project button for the GitHub repository containing the code (aks-nodejs-argocd
).
When prompted to select your config.yml file, click the Fastest option and type main
as the branch name. CircleCI will automatically locate the config.yml
file. Click Set Up Project.
The workflow will run, but will soon display a status
of Failed
. This is because you need to set up a user key and configure the environment variables.
To set up the user key, go to Project Settings and click SSH Keys from the left panel. In the User Key section, click Authorize with GitHub. The user key is needed by CircleCI to push changes to your GitHub account on behalf of the repository owner during the execution of the workflow.
To configure the environment variables, click Environment Variables. Select the Add Environment Variable option. On the next screen, type the environment variable and the value you want to assigned to it.
The environment variables used in the file are:
APP_NAME
: Container Image Name (aks-nodejs-argocd)ARGOCD_PASSWORD
: ArgoCD portal passwordARGOCD_SERVER
: ArgoCD Server IP AddressARGOCD_USERNAME
: ArgoCD portal username (admin)AZURE_PASSWORD
: Azure Account PasswordAZURE_USERNAME
: Azure Account UsernameCLUSTER_NAME
: AKS Cluster Name (NodeCluster)DOCKER_LOGIN
: Docker Hub UsernameDOCKER_PASSWORD
: Docker Hub PasswordGITHUB_EMAIL
: GitHub Account Email AddressRESOURCE_GROUP
: AKS Resource Group (NodeRG)SSH_FINGERPRINT
: SSH Fingerprint of User Key used for pushing the updated Docker tag to GitHub
To locate the SSH Fingerprint, go to Project Settings and select SSH Keys from the sidebar. Scroll down to the User Key section and copy the key.
Re-run the workflow. This time the status
will show Success
.
You will also find another pipeline having the status
as Not Run
. That is because you have explicitly instructed CircleCI to skip the pipeline by including [skip ci]
in the commit message. When CircleCI commits the updated configuration files to GitHub, [skip ci]
prevents a self-triggering loop of the workflow.
Monitoring the application on ArgoCD Dashboard
A status
that shows Success
when the workflow is re-run means that the application has been deployed on the AKS cluster.
To observe and monitor the resources that are currently running on the AKS Cluster, log in to the ArgoCD Web Portal.
Earlier in this tutorial, you learned how to fetch the ArgoCD Server IP, username, and password for logging in to the portal. After logging in, you will be on the Applications page.
Click the application name. You will be redirected to a page with the tree view of all resources running on the AKS Cluster and their real-time status.
Accessing the application on AKS
To access the application, you need the external IP address of the cluster. You can use the Azure CLI to find the External-IP
.
Configure kubectl
to connect to AKS using this command:
az aks get-credentials --resource-group NodeRG --name NodeCluster
You created all the resources in the nodejs
namespace. To get all the resources in that namespace, use this command:
kubectl get all --namespace nodejs
Copy the External-IP
corresponding to service/nodejs
.
You can access the application at http://<EXTERNAL-IP>
.
Conclusion
In this tutorial, you learned how to deploy your applications continuously on a Kubernetes cluster following GitOps practices with ArgoCD. This included configuring an automated CI pipeline. With the pipeline properly configured, any changes made to the application code are instantly updated on the application URL. Say goodbye to manually configuring and deploying applications on Kubernetes.
As a bonus, you can change the values of the environment variables to use the CircleCI configuration file for similar applications.
The complete source code for this tutorial can also be found here on GitHub. Sign up for your free account and add continuous deployment to your Kubernetes toolkit today.