Part III : GitOps Deployments on EKS with Argo CD and Ansible
What Ansible ?
Ansible is an open-source automation tool that simplifies IT infrastructure management by using simple, human-readable YAML files called playbooks to automate repetitive tasks like server configuration, application deployment, and system updates
What is GitOps ? ArgoCD ?
GitOps is a principal of managing infrastructure and application configurations through Git repositories.
ArgoCD A tool that automates applications deployments to Kubernetes clusters based on the code hosted in the Git repo.
Prepare prerequisites
Install python
First you’ll need python installed on your machine.
$ sudo apt update
$ sudo apt install python3Install pip
To install pip, run the following commands :
$ python3 -m ensurepip --upgradePython comes with an ensurepip module, which can install pip in a Python environment.
Install Ansible
Use pip in your selected Python environment to install the full Ansible package for the current user:
$ python3 -m pip install --user ansibleOnce everything is installed, let’s get started!
Kubernetes resources
In this section, I’ll walk through all the Kubernetes configuration files that need to be deployed to the cluster.
Database StatefulSet
# Postgres StatefulSet
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ecommdb
spec:
selector:
matchLabels:
app: ecommappdb
serviceName: ecommdbsvc
replicas: 1
template:
metadata:
labels:
app: ecommappdb
spec:
containers:
- name: dbcontainer
image: postgres
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: ecommconfig
key: POSTGRES_DB
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: ecommsecret
key: db-username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: ecommsecret
key: db-password
---
apiVersion: v1
kind: Service
metadata:
name: ecommdbsvc
spec:
clusterIP: None
selector:
app: ecommappdb
ports:
- port: 5432
targetPort: 5432Backend Deployment
# Backend Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ecommback
spec:
replicas: 1
selector:
matchLabels:
app: ecommappback
template:
metadata:
labels:
app: ecommappback
spec:
imagePullSecrets:
- name: ecr-registry-key
containers:
- name: backcontainer
image: 314146337686.dkr.ecr.us-east-1.amazonaws.com/ecomm.ci.cd.back:0.4.1-125
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: ecommconfig
env:
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: ecommsecret
key: db-username
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: ecommsecret
key: db-password
---
apiVersion: v1
kind: Service
metadata:
name: ecommbacksvc
spec:
selector:
app: ecommappback
ports:
- protocol: TCP
port: 8080
targetPort: 8080Frontend Deployment
# front deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ecommfront
spec:
replicas: 1
selector:
matchLabels:
app: ecommappfront
template:
metadata:
labels:
app: ecommappfront
spec:
imagePullSecrets:
- name: ecr-registry-key
containers:
- name: frontcontainer
image: 314146337686.dkr.ecr.us-east-1.amazonaws.com/ecomm.ci.cd.front:0.0.2-125
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
env:
- name: BACKEND_HOST
value: "ecommbacksvc"
- name: BACKEND_PORT
value: "8080"
---
apiVersion: v1
kind: Service
metadata:
name: ecommfrontsvc
spec:
selector:
app: ecommappfront
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: ecommconfig
data:
spring.datasource.url: jdbc:postgresql://ecommdbsvc:5432/ecommdb
app.data.number-of-customers: "100"
app.data.number-of-products: "50"
app.data.number-of-orders: "20"
spring.application.name: "ecomm-ci-cd"
spring.profiles.active: "dev"
server.port: "8080"
spring.graphql.cors.allowed-origins: "*"
POSTGRES_DB: "ecommdb"Secret
apiVersion: v1
kind: Secret
metadata:
name: ecommsecret
type: Opaque
data:
db-username: YWJkZXNzYW1hZA==
db-password: YWJkZXNzYW1hZA==Ansible playbooks
Inventory
First, let’s configure the Ansible inventory to define where our tasks will run. Along with the playbooks, create a file called hosts (or inventory.ini) to define your target hosts.
[all]
localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3In my case, I’m running Ansible locally, so the inventory is very simple! No IP addresses or SSH keys needed.
Configuration
Ansible reads its configuration file in /etc/ansible/ansible.cfg, this config file define various settings and parameters that influence how Ansible operates. To customize this configuration, create an ansible.cfg file in your project directory.
[defaults]
inventory = hosts
log_path = ./ansible.logThis setup points to the local hosts file and saves execution logs to ansible.log for debugging.
Setup Playbook
---
- name: Setup ArgoCD on EKS
hosts: localhost
connection: local
vars:
ns: argocd
region: "us-east-1"
cluster_name: "ecomm-eks-cluster"
aws_ecr_server: "314146337686.dkr.ecr.us-east-1.amazonaws.com"
tasks:
- name: Update Kubeconfig for EKS using aws cli
shell: |
aws eks update-kubeconfig \
--region {{ region }} \
--name {{ cluster_name }}
- name: Install pre-requisites
ansible.builtin.pip:
name:
- openshift
- pyyaml
- kubernetes
- name: Create k8s ArgoCD namespace
kubernetes.core.k8s:
name: "{{ ns }}"
api_version: v1
kind: Namespace
state: present
- name: Create Docker Registry secret for AWS ECR
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: ecr-registry-key
namespace: "{{ ns }}"
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ dockerconfjson | to_json | b64encode }}"
vars:
ecr_token: "{{ lookup('pipe', 'aws ecr get-login-password --region ' + region) }}"
dockerconfjson:
auths:
"{{ aws_ecr_server }}":
username: "AWS"
password: "{{ ecr_token }}"
- name: Install ArgoCD
kubernetes.core.k8s:
src: https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
state: present
namespace: "{{ ns }}"
- name: Install ArgoCD Image Updater
kubernetes.core.k8s:
src: https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml
state: present
namespace: "{{ ns }}"
- name: Wait till ArgoCD server is deployed
kubernetes.core.k8s_info:
api_version: v1
kind: Deployment
namespace: "{{ ns }}"
wait: yes
wait_condition:
type: Available
status: "True"
wait_timeout: 300
wait_sleep: 10
- name: Get initial ArgoCD admin password
kubernetes.core.k8s_info:
api_version: v1
kind: Secret
namespace: "{{ ns }}"
name: argocd-initial-admin-secret
register: admin_secret
- name: Decode and log admin password
debug:
msg: "Iinitial Admin Password : {{ admin_secret.resources[0].data.password | b64decode }}"
when: admin_secret.resources | length > 0
- name: Expose ArgoCD LoadBalancer service
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Service
metadata:
name: argocd-lb
namespace: "{{ ns }}"
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 8080
selector:
app.kubernetes.io/name: argocd-serverThis Ansible playbook automates the setup of ArgoCD on the Amazon EKS Kubernetes cluster we’ve created in the part 2 of this serie. It configures kubectl access to the EKS cluster, creates the necessary namespace, sets up AWS Elastic Container Registery authentication, installs ArgoCD and the ArgoCD Image Updater, retrieves the initial admin password, and exposes ArgoCD through a LoadBalancer service for external access.
Deployment Playbook
- name: Deploy ArgoCD on EKS
hosts: localhost
vars:
ns: "argocd"
tasks:
- name: Create ArgoCD Application
kubernetes.core.k8s:
state: present
definition:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ecomm-ci-cd
namespace: "{{ ns }}"
annotations:
argocd-image-updater.argoproj.io/image-list: |
backend=314146337686.dkr.ecr.us-east-1.amazonaws.com/ecomm.ci.cd.back,
frontend=314146337686.dkr.ecr.us-east-1.amazonaws.com/ecomm.ci.cd.front
argocd-image-updater.argoproj.io/backend.update-strategy: latest
argocd-image-updater.argoproj.io/frontend.update-strategy: latest
argocd-image-updater.argoproj.io/write-back-method: git
spec:
project: default
source:
repoURL: "https://github.com/abdessamadabidar/GitOps.git"
targetRevision: HEAD
path: ecomm-ci-cd
destination:
server: "https://kubernetes.default.svc"
namespace: default
syncPolicy:
automated:
selfHeal: true
prune: trueThis Ansible playbook creates an ArgoCD Application that automatically deploys our app from a Git repository to Kubernetes, with automated image updates from AWS ECR and self-healing capabilities to keep up the desired application state.
Open your browser and paste the following link: http://<EXTERNAL-IP>
Here we go! This is how our application is synced and deployed using ArgoCD.
Complete Jenkins Pipeline
To complete the Jenkins pipeline that we’ve started in the part I of this project, here are additional stages for Ansible.
stage('Checkout IaC') {
steps {
git branch: 'master', credentialsId: 'github-creds', url: 'https://github.com/abdessamadabidar/IaC.git'
}
}
stage('Ansible: Run Setup ArgoCD playbook') {
steps {
dir('ecomm-ci-cd/ansible') {
sh 'ansible-playbook -i hosts setup-argocd-playbook.yaml'
}
}
}
stage('Ansible: Run Deploy ArgoCD playbook') {
steps {
dir('ecomm-ci-cd/ansible') {
sh 'ansible-playbook -i hosts deploy-argocd-playbook.yaml'
}
}
}The first stage checks out the IaC repository from GitHub. It pulls the master branch so the pipeline always uses the latest infrastructure code.
Get Abdessamad Abidar’s stories in your inbox
Join Medium for free to get updates from this writer.
The last two stages move into the ansible directory and run the playbooks.
Demo
In this section, I’ll show you a few screenshots of our demo application. First, let’s check the frontend service to get its external IP address.
Open your browser and paste the following link: http://<EXTERNAL-IP>
Conclusion
Wow! What a journey it’s been to get here. We’ve covered so many tools and technologies in this project.
See you in the next blog!
Practical DevOps Pipeline: Part I — Continuous Integration with Jenkins
Practical DevOps Pipeline: Part II — Provisioning AWS EKS Cluster with Terraform









