In this project, I will guide you in building a pipeline with GitHub actions that will deploy an infrastructure using Terraform(VPC, public and private subnets, nat gatway) and run containers on EKS, updates EKS each time there's a new application version. The new version is automatically built each time the developper pushes application code to the code repository. Necessary security scans are run in order to ensure a secure and complaint code. The infrastructure code and application code are stored in two different repositories.
Architecture
How to build it
Prepare Repositories
Login to your github account, choose a location and open gitbash terminal on your local machine and clone the code
git clone https://github.com/Ndzenyuy/githubactions-infra.git
git clone https://github.com/Ndzenyuy/githubactions-appl.git
rm -rf githubactions-infra/.git/ githubactions-infra/.github/
rm -rf githubactions-appl/.git/ githubactions-appl/.github/
The two repositories contain the infrastructure(githubactions-infra) and the application code(githubactions-appl). Now we have to initialize git for code management, first for the infrastructure. Back to the console, run the following
cd githubactions-infra
git init
code .
The above code will open the infrastructure in VScode and initialise git. Under the source control icon in VScode, publish the branch to GitHub, choose public repository. Return to the console and run the following code (make sure you are in the githubactions-infra folder)
cd ../githubactions-appl
git init
code .
Another VScode window will open, Under the source control icon in VScode, publish the branch to GitHub, choose public repository. We are now ready to deploy our application.
GitHub Secrets
Here we are going to create and store AWS access keys, S3 bucket, ECR repository and store in Github secrets.
Create IAM user
Go to AWS console and create an admin user IAM -> User -> create user -> attach policies -> administrator access -> create access keys
On github, githubactions-infra repository go to settings -> Secrets and variables -> actions -> new repository secret and store the following keys
- Name: AWS_ACCESS_KEY_ID, Secret:
- Name: AWS_SECRET_ACCESS_KEY: secret:
Do the same for the repository githubactions-appl, store both secrets with the same name.
Create S3 bucket
Goto AWS console, and create an S3 bucket, we can call it "githubactions-infra-xxx" (xxx is a random number to make bucket unique)
Back to Github secrets(githubactions-infra), create a new secret having the bucket name:
Name: BUCKET_TF_STATE, secret: githubactions-infra-xxx
Create ECR repository
On to AWS console, Elastic Container Registry -> Create repository -> name: githubactions-appl -> create repository. Copy the repository URI(XXXXXXXXX.dkr.ecr.us-east-2.amazonaws.com/githubactions-appl)
Back to github secrets (githubactions-appl), Create another secret
Name: REGISTRY, secret: XXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com
githubactions-infra secrets should be similar to
Terraform Code Adjustments
In the githubactions-infra repo code, open it in VSCode and move to the folder terraform, modify the contents of terraform.tf while updating the name of your s3 bucket and the region where the infrastructure will be deployed
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.25.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5.1"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0.4"
}
cloudinit = {
source = "hashicorp/cloudinit"
version = "~> 2.3.2"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23.0"
}
}
backend "s3" {
bucket = "<Your bucket name>"
key = "terraform.tfstate"
region = "your working region"
}
#required_version = "~> 1.5.1"
required_version = "~> 1.10.1"
}
Github actions workflow
In this workflow terraform will not apply the code for the infrastructure to be created(staging), but when a pull request is made, the pipeline is triggered and this time and the pipeline get applied(main), we need to put conditions. In the file .github/workflow/terraform.yml, we should have the code
name: "Github Actions IaC"
on:
push:
branches:
- main
- stage
paths:
- terraform/**
pull_request:
branches:
- main
paths:
- terraform/**
env:
# Credentials for deployment to AWS
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# S3 bucket for the Terraform state
BUCKET_TF_STATE: ${{ secrets.BUCKET_TF_STATE }}
AWS_REGION: us-east-1
EKS_CLUSTER: infra-eks
jobs:
terraform:
name: "Apply terraform code changes"
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: ./terraform
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Terraform with specified version on the runner
uses: hashicorp/setup-terraform@v2
#with:
# terraform_version: 1.6.3
- name: Terraform init
id: init
run: terraform init -backend-config="bucket=$BUCKET_TF_STATE"
- name: Terraform format
id: fmt
run: terraform fmt -check
- name: Terraform validate
id: validate
run: terraform validate
- name: Terraform plan
id: plan
run: terraform plan -no-color -input=false -out planfile
continue-on-error: true
- name: Terraform plan status
if: steps.plan.outcome == 'failure'
run: exit 1
- name: Terraform Apply
id: apple
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false -parallelism=1 planfile
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Get Kube config file
id: getconfig
if: steps.apple.outcome == 'success'
run: aws eks update-kubeconfig --region ${{ env.AWS_REGION }} --name ${{ env.EKS_CLUSTER }}
- name: Install Ingress controller
if: steps.apple.outcome == 'success' && steps.getconfig.outcome == 'success'
run: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.3/deploy/static/provider/aws/deploy.yaml
Test Pipeline
We need to first test the pipeline with a dry run by creating a staging branch. This will trigger the pipeline but will not apply so we are sure it can run. Got to the githubactions-infra window in VScode and open a new terminal, make sure any changes have been committed then run the following:
git branch -b stage
This will create a new branch, on the source control icon in VSCode, publish the branch. This will run the pipeline and skip the apply step because there is a a condition set to only run apply when the main branch is active
If the run is successful, we can now be certain the pipeline will run, if it fails, read the logs, the terraform version might have been outdated. Change it to the new and push the code again.
Merge the staging branch to main branch, this will cause the pipeline again to run but this time, will apply the changes and setup the infrastructure on AWS eks.
App workflow
The next workflow will use the infrastructure created from the previous job, then deploy the application with best practices.
Create an organization in sonarcloud
Open the browser and navigate to www.sonarcloud.io, click the "+" at the top right corner and create a new organization -> create one manually
name: github-actions-cicd
key: github-actions-cicd -> Create
Click analyze new project
Organization: github-actions-cicd
display name: github-actions
Project key: github-actions -> Next
The new code for this project will be based on: Previous version=true
-> Create project
Choose analysis method: With Github actions:
Copy the sonar token and paste it in notepad, click on information on the left, copy the values of organization, project key and create secrets in Github repo githubactions-infra
name: SONAR_TOKEN, secret: <COPIED TOKEN>
name: SONAR_ORGANIZATION, secret: <COPIED ORGANIZATION>
name: SONAR_PROJECT_KEY, secret: <COPIED PROJECT KEY>
name: SONAR_URL, secret: https://sonarcloud.io
Create quality gate in sonarcloud -> Administration -> Organization's settings -> Create:
name: githubactionsQG
add condition:
where: on overall code = true
Quality gate fails when Bugs > 50
select project: github-actions
Go back to project administration, select Quality gate then "githubactionsQG"
Deployment to EKS
Install helm charts
In terminal, run the code(for ubuntu users, other users can check their operating system installation)
sudo apt install helm
Configure helm
Change to the directory where the project is githubactions-appl, then run
helm create githubactions-charts
A new directory(githubactions-charts) will be created, next
mv githubactions-charts helm/
rm -rf helm/githubactions-charts/templates/*
cp kubernetes/vpro-app/* helm/githubactions-charts/templates/
Deploy code
We need to first git the workflow the permissions to write trivy scan results in the security tab. To do so, Navigate to githubactions-appl repository on GitHub. Click on the Settings tab; In the sidebar, click on Actions. Under the Actions permissions section, click general and scroll down to Workflow permissions and select Read and write permissions then save.
Back to VSCode, commit and push the changes. In github actions, run the workflow. This time the pipeline will not be triggered automatically(as configured in our code), we'll have to trigger it manually on our github actions tab.
Check the security tab in github actions to see the report of the security scans with trivy
Modify domain name
In the file helm/githubactions-charts/templates/vproingress.yaml, modify the domain name to your own after creating a hosted zone in Route53
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vpro-ingress
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: github.ndzenyuyjones.link
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 8080
Modify the line - host: github.ndzenyuyjones.link to contain your domain name, will put a CNAME record in the hosted zone.
On the AWS console, we need to search and copy the url of the load balancer created for this project by the previous stack, copy the url and create a hosted zone with a cname record
Type: CNAME
Name: githubactions
value:
Wait about 5-10mins and search the link of our project on a browser github.ndzenyuyjones.link(use the link you created in the hosted zone), the landing page should appear.
Login with
username: admin_vp
passwrd: admin_vp
Clean up
Remove ingress controller
First we need to make sure there is no existing kubeconfig file in our local machine by running
rm -rf /.kube/config
aws eks update-kubeconfig --region us-east-1 --name infra-eks
Now navigate to the repository on your local machine, where ever it was cloned and run
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.3/deploy/static/provider/aws/deploy.yaml
Now delete the helm list, you can first run helm list and copy the name of the list to be uninstalled
helm uninstall rhena-stack
Move to terraform folder
cd terraform/
Then initialize terraform
Change the content of terraform.tf file to match the following
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.25.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5.1"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0.4"
}
cloudinit = {
source = "hashicorp/cloudinit"
version = "~> 2.3.2"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23.0"
}
}
backend "s3" {
bucket = "githubactions-infra-012"
key = "terraform.tfstate"
region = "us-east-1"
}
required_version = "~> 1.5.1"
}
Then we initialise the local machine to have the same state as the created cloud infrastructure.
terraform init
Now we run destroy command, when prompted type "yes"
terraform destroy
Top comments (0)