The idea of continuous integration and continuous delivery(CI/CD) becomes wildly used for version control repositories. There are tons of blogs defining what is CI/CD. I get familiar with CI/CD from my work experience in Circleci. In my understanding, CI is a procedure to verify each small changes will not affect the functionality of a project and not violate the tests of a project in a workflow or pipeline. After CI ends, CD picks up the procedure to achieve some deployment such as release packages, sync static website and publish docs etc. This is how I understand CI/CD according to my experience. With CI/CD procedure, development will reduce the breaking of functionality when numbers of people contributing to a same project and get deployed easily.
How to do CI/CD with Circleci
After we set up repositories in Circleci, Circleci always reminds us to add a config.yml
file. So we need to create a
.circleci
folder at the top level of the repo. Under the .circleci
folder, we create a config.yml
file. With this
config file, then Circleci will run its workflow defined in the config file.
In Circleci version 2.1, a config.yml
file usually consists of three components, executors
, jobs
and workflows
.
executors
is used to config the images in the Circleci tasks and work directory in Circleci. Defining executors is
similar to write a Dockerfile
. jobs
is used to define all checks, usually consisting of build
, test
for CI,
deploy
, release
for CD. workflows
is used to define the dependency and order of the jobs. We can define the
deployment branch in workflows
as well.
Here is a sample template of config.yml
version: 2.1
executors:
name:
docker:
# define image
working_directory: # define working directory
jobs:
build:
# check the construction of dependency and environment
executor: name
steps:
- checkout
# define steps to construct environment
- run:
name: # define name
command: # add command
# add other jobs
test:
...
workflows:
version: 2.1
flow_name:
jobs:
- build
- test:
requires:
- build
# above means test job will be executed after build step
# if steps depends on the same step, those steps will run parallel in circleci
I am going to cover two examples to demonstrate details about how to write the config file.
- How to add test database in Circleci
- How to deploy static website content in Circleci
How to add test database in Circleci
When we have some database functionality needed to be tested, setting a test database in Circleci is necessary.
This is how the config.yml
roughly looks like,
version: 2.1
executors:
# can change to other name
name:
docker:
# If the codebase is python
- image: circleci/python:3.7.4
environment:
# add database url as environment variable in circle
TEST_DATABSE_URL: postgresql://root@localhost:5432
# database image is required as well for database test
- image: circleci/postgres
working_directory: ~/repo
jobs:
build:
executor: name
steps:
- checkout
- run:
name: Install depenedency
command: |
# command to install dependency
# following is how to set up database
- run: sudo apt-get update
- run: sudo apt-get install postgresql-client-11
# then we can create user or database so that we can run test
- run:
name: Setup database
command: |
# command to set up database with postgresql-cli
- run:
name: migrate table
command: |
# need to exist migration in reop so that all tables can be migrated in circleci
# in other words, migration can guarantee the test database have the same tables as
# real database
# then add other jobs
test:
...
I only include the database set up part, which I spent most of my time on when I first tried to add database test in Circleci. For the rest of jobs, they depends on the actual requirements of CI/CD. The way to write the config file is like writing a bash file, adding what we would like to do in the Circleci workflow with specific syntax line by line. Finally, define the workflows with existing jobs and then it is good to go.
How to deploy static website content in Circleci
I also tried to add Circleci to my personal website repo so that every time I merge new content in master, Circleci can automatically help me sync my new content to my S3 bucket.
This is how the config.yml
roughly looks like,
# similar with other config files
version: 2
jobs:
build:
docker:
- image: cibuilds/hugo:latest
working_directory: ~/repo
environment:
HUGO_BUILD_DIR: ~/repo/public
steps:
- checkout
- run:
name: Add Theme
command: |
mkdir themes
cd themes
git clone git@github.com:luizdepra/hugo-coder.git
- run:
name: Generate Public Folder
command: |
HUGO_ENV=production hugo -v -d $HUGO_BUILD_DIR
- run:
name: Install AWS Cli
command: |
apt-get update && apt-get -y install python-pip
pip install awscli
- run:
name: Test Generated HTML Files
command: |
htmlproofer $HUGO_BUILD_DIR --allow-hash-href --check-html \
--empty-alt-ignore --disable-external
- deploy:
name: deploy to AWS
command: |
if [ "${CIRCLE_BRANCH}" = "master" ]; then
aws s3 sync $HUGO_BUILD_DIR s3://zhen404.com --delete
else
echo "Not master branch, dry run only"
fi
This config file is a little bit different from previous one. I wrote all procedures in one pipeline instead
of creating multiple components for a workflow. After I figure out how to restore the environment in build
step, I will refactor this config file in the similar way as the previous example.
Lesson Learned:
I felt like I failed because I didn’t pay for circleci for the disk space issue before. But the real reason is
that I miss the -y
flag while using apt-get
. With this flag, the apt-get
will not ask if I would like
to continue when disk space reach to a limit. Also, when the workflow involve AWS operation, we need to
add the aws credentials in Circleci setting.
Bottom Line
After knowing how config.yml
is written, I think CI/CD looks not that hard in practice. But it’s worth
writing more config files when I get chance so that I can have more experience on CI/CD different situations.