GitHub Actions: End to end CI/CD Pipeline for Django
As promised I am back! I hope you all enjoyed my last blog on GitHub Actions: Basics. Today I will show you how you can take earlier knowledge and make an end to end CI/CD pipeline for a django project using GitHub Actions.
What will we do in here?
What I want to show here is how developers work in their day to day lives. How they work with commits, pushes, pull requests, merges and ci/cd pipelines. For this I will be using a django project. But you all can use any project you like because the basics are all same overall. What might be different is the application specific codes.
- We will open a repo. Create a
master
anddevelop
branch. Any push tomaster
branch will be deployed to production anddevelop
to the staging. - All the developers working in the projects should commit their commits to any
feature/
branches. For this blog we will be using a branch namedfeature/feature1
- Generally when the work finishes in the
feature/feature1
branch a pull request is submitted todevelop
branch. On submit request aworkflow
should run which will check if the formatting of the codes are fine and tests are working alright. You never want to push untested codes to production, right? - On passing the tests and approval from another
reviewer
in this case my work account in GitHub @imshetu. I will work on feature branches using my personal account @nashmaniac. When I submit a pull request I will select @imshetu as thereviewer
. - On push to
develop
branch we will again run the tests and deploy the code to staging server in Google Cloud. - Later we will submit another pull request from
develop
tomaster
At this time same code formatting and testing will be checked and approval will be given from a reviewer. - On push to
master
we will just run the test and deploy it to production.
Few things to remember, inour case staging
and production
are the same server. We didn’t want to complicate things by having two servers to maintain. Let’s see this graphically to understand more.
So I will be writing a workflow which will be triggered in pull_request
is submitted to or code is push
to develop
or master
branch. Our workflow will contain three jobs
- Health Check Job: For all the testing and formatting check.
- Packaging Job: For docker build and update registry
- Deployment Job: Deploying the published image to cloud.
At this point you might think we have workflows name django
and gke
in GitHub Actions already, why rewrite it again? I would say if you are comfortable with it, you can definitely do it. In here I will write it step wise so that you can figure out how all these pieces work together.
I know, it will be a bit long. But I assure you, it’s worth the wait.
Application Setup
- Create a repo in GitHub.
- Clone the repo in your local. I used ssh key for authentication. If you don’t have one use the https link in clone pop up box.
git clone git@github.com:nashmaniac/github-django-actions.git
3.a. Go to project directory and create a new branch named develop
by running git checkout -b develop
Now push it to GitHub by running git push -u origin develop
3.b. Here add some branch protection rules to develop
and master
branch so that no code get pushed to those branch without review.
4. Create another branch named feature/feature1
We will do all our work in here.
5. Initialize a python virtualenv directory, activate it and install django
psycopg2-binary
pycodestyle
gunicorn
inside the virtualenv and make a requirements.txt
by running pip freeze > requirements.txt
6. Create a .gitignore
file and added all the necessary files and folders you don’t want in GitHub.
7. Initialize django project running django-admin startproject app .
8. We will create an app in django named users
and add some tests there. Create the app using python manage.py startapp users
9. Add a test in users/test.py
file like below
10. We need to change out database config in app/settings.py
file to make sure our postgres database works. For this delete DATABASES
variable in app/settings.py
and paste the code below.
12. We will add a Dockerfile
to our project directory which will build our application into an image. We won’t be discussing as it is out of scope. You can find it in the repo.
13. I will be adding a folder named k8s
which will contain our helm
chart. We will use it during our deployment. For keeping this simple I won’t be discussing it here as it would be out of scope a bit.
14. Create a workflow file name .github/workflows/ci.yaml
Let’s write the workflow!
I know it’s a lot of steps earlier. Let’s write our workflow now. After finishing, we will be creating a service account in Google Cloud for deployments and declare few secret in GitHub repo settings and we will test out the whole shebang.
I will add the whole workflow file here will enough comments to make it understandable. Please go through the comments. It will make it clear.
Great, I hope the upper gist makes the workflow clear to us. Few more steps and we are good for testing.
Infrastructure & Secrets Setup
- We need to create a kubernetes cluster in Google Cloud and a service account with
Kubernetes Engine Admin
Artifact Registry Writer
&Storage Admin
permission so that it can push and deploy our application. Make sure you download the service account as json in your local.
- You can setup those with admin permission without any issues because those service accounts credentials won’t be available in logs as those will be set as secrets.
- Go to Settings tab in GitHub Repo and set your secrets.
- Few things to remember. GKE_EMAIL is the email of service account key. You will find it as client_email if you open the json file you downloaded and GKE_PASSWORD would be the content of json file.
Now let’s make a commit in your feature/feature1
branch and push it.
Moment of Truth
I know you all are waiting for this moment. As our code is pushed to feature/feature1
branch. We are all set. I will be adding my work account to this repo so that I can review the pull request.
- Create a pull request and add a reviewer.
- When I merged the Pull Request after approval from another account in here, A workflow was run and failed due to some helm file issue. So I need to submit another pull request from
feature/feature1
todevelop
and it failed again. So I made some changes and pushed. At the Pull Request #3, it got passed. After merge this is log of push event ondevelop
branch workflow run.
- Let’ check what is deployed actually. I have a
Cluster IP Service
in kubernetes so I will just port forward it to my local port and check. First connect to kubernetes cluster and runkubectl port-forward svc/app-service 8000:80
- Let’s submit a pull request from
develop
tomaster
and approve that. - Merged to master worked fine but workflow failed due to some nasty
helm upgrade
issue. But I was glad at this point to show you guys the real scenario. Because in real life troubleshooting the bugs in your code is the most common thing we encounter everyday.
So at last it worked on Pull Request #7. The merged code from develop
to master
ran successfully.
What I really want to highlight here is 65d2b9f
is the short sha of the master branch commit and the docker image deployed in our cloud is also app:sha-65d2b9f
which actually means our built image is deployed successfully.
So finally all worked out. Yay !!!
I was super excited while I was planning to write this article. Yes, faced few impediments during writing this due to helm related issue a bit. But I am glad now that I could finish this. You can find all the codes in here.
Lastly, you can do amazing things with GitHub workflow like opening an issue if workflow fails and assign them to developers, can use CODEOWNERS feature to automatically request review from users and can post slack message too in your slack. This simple automation can make your life lot easier. Hopefully I will write another one on this.
Stay fine and take care of yourself during this COVID lockdown.
Happy Coding !!!