What is CI/CD?
In software engineering, CI/CD or CICD generally refers to the combined practices of continuous integration and either continuous delivery or continuous deployment. CI/CD bridges the gaps between development and operation activities and teams by enforcing automation in building, testing and deployment of applications.
Let’s start the execution step by step.
CI (Continues Integration)
Step 1
Let’s start creating a new workflow in GitHub actions that will perform those tasks. in your root rails project.
mkdir -p .github/workflows
touch .github/workflows/main.yml
Step 2
The mail.yml where we defined our workflow CI and CD. We name our workflow CI/CD, Then list the name of our events which will trigger our workflow.
# main.yml
name: CI/CD
on: [push, pull_request]
Step 3
Use the ubuntu latest image to install all necessary libraries and PostgreSQL as database
name: CI/CD | |
on: [push, pull_request] | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
services: | |
db: | |
image: postgres:11 | |
ports: ["5432:5432"] | |
env: | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
options: >- | |
--health-cmd pg_isready | |
--health-interval 10s | |
--health-timeout 5s | |
--health-retries 5 | |
redis: | |
image: redis | |
ports: ["6379:6379"] | |
options: --entrypoint redis-server |
services can be used to create additional containers for a job or steps. In our case, we are using it to spin-off postgres and redis service.
Step 4
We can define a sequential task that we want to perform in the machine with steps statement.
Next thing we will do is check out our rails app. Github provides official, ready to use actions an one of those is action/checkout we can directly use this action directly using statement use.
Similarly other actions actions/setup-ruby and bolares/actions-yarn is used to check out the ruby version and yarn package manager that we want to use for our rails app.
name: CI/CD | |
on: [push, pull_request] | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
services: | |
db: | |
image: postgres:11 | |
ports: ["5432:5432"] | |
env: | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
options: >- | |
--health-cmd pg_isready | |
--health-interval 10s | |
--health-timeout 5s | |
--health-retries 5 | |
redis: | |
image: redis | |
ports: ["6379:6379"] | |
options: --entrypoint redis-server |
In the above snippet 👆, we just added steps for setting up ruby environment and yarn.
More about bolares/actions-yarn is here
Step5
Once the code has been checked out and correct ruby version is set up, we install the gems using bundler and then run the specs
Let’s add one more step for setting up our rails application.
steps: | |
- uses: actions/checkout@v1 | |
- name: Setup Ruby | |
uses: actions/setup-ruby@v1 | |
with: | |
ruby-version: 2.6.x | |
- uses: borales/actions-yarn@v2.0.0 | |
with: | |
cmd: install | |
- name: Build and run test | |
env: | |
DATABASE_URL: postgres://postgres:@localhost:5432/test | |
REDIS_URL: redis://localhost:6379/0 | |
RAILS_ENV: test | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
run: | | |
sudo apt-get -yqq install libpq-dev | |
gem install bundler | |
bundle install --jobs 4 --retry 3 | |
bundle exec rake db:create | |
bundle exec rake db:migrate | |
bundle exec rspec . |
In the above 👆 snippet, we are adding some necessary environment variables and executing the necessary commands.
First, we installed bundlergem, then create and migrate the database and finally running Rspec test cases.
Note: I’m using Rspec testing library you can put other as well
That’s it, we successfully integrate CI workflow for our Rails app successfully, now if push the changes you can see GitHub actions start executing.
CI Workflow
You can see your actions are executing successfully 🙂.
If you missed something below 👇 is the full snippet for CI workflow.
name: CI/CD | |
on: [push, pull_request] | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
services: | |
db: | |
image: postgres:11 | |
ports: ["5432:5432"] | |
env: | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
options: >- | |
--health-cmd pg_isready | |
--health-interval 10s | |
--health-timeout 5s | |
--health-retries 5 | |
redis: | |
image: redis | |
ports: ["6379:6379"] | |
options: --entrypoint redis-server | |
steps: | |
- uses: actions/checkout@v1 | |
- name: Setup Ruby | |
uses: actions/setup-ruby@v1 | |
with: | |
ruby-version: 2.6.x | |
- uses: borales/actions-yarn@v2.0.0 | |
with: | |
cmd: install | |
- name: Build and run test | |
env: | |
DATABASE_URL: postgres://postgres:@localhost:5432/test | |
REDIS_URL: redis://localhost:6379/0 | |
RAILS_ENV: test | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
run: | | |
sudo apt-get -yqq install libpq-dev | |
gem install bundler | |
bundle install --jobs 4 --retry 3 | |
bundle exec rake db:create | |
bundle exec rake db:migrate | |
bundle exec rspec . |
CD(Continuous delivery)
Now Let’s move to the CD(continues delivery) part, before moving further you must have an account on AWS
Step1
Install CLI elastic beanstalk on your local machine.
brew install aws-elasticbeanstalk
Go to your project directory and initialize elastic beanstalk using the command
eb init
During init, elastic beanstalk shows some CLI questions & it will create config.yml like below 👇
branch-defaults: | |
master: | |
environment: staging | |
group_suffix: null | |
global: | |
application_name: testing-rails | |
branch: null | |
default_ec2_keyname: xxxx | |
default_platform: Puma with Ruby 2.6 running on 64bit Amazon Linux | |
default_region: ap-south-1 | |
include_git_submodules: true | |
instance_profile: null | |
platform_name: null | |
platform_version: null | |
profile: eb-cli | |
repository: null | |
sc: git | |
workspace_type: Application |
Step2
Create a project with Postgres.
eb create staging -db.engine postgres
Above 👆command will start creating our project ready for deployment.
2020-09-12 14:59:43 INFO createEnvironment is starting. | |
2020-09-12 14:59:44 INFO Using elasticbeanstalk-ap-south-1-575689566956 as Amazon S3 storage bucket for environment data. | |
2020-09-12 15:00:10 INFO Created target group named: arn:aws:elasticloadbalancing:ap-south-1:575689566956:targetgroup/awseb-AWSEB-18R2XQ1R8YMFV/e7c91bd35ed8fb5a | |
2020-09-12 15:00:10 INFO Created security group named: sg-0e97209914d26387b | |
2020-09-12 15:00:26 INFO Created security group named: awseb-e-urp3iwwxh3-stack-AWSEBSecurityGroup-1AO6G8UOUXC80 | |
2020-09-12 15:00:26 INFO Created Auto Scaling launch configuration named: awseb-e-urp3iwwxh3-stack-AWSEBAutoScalingLaunchConfiguration-509D9VK4UOUY | |
2020-09-12 15:00:26 INFO Created security group named: awseb-e-urp3iwwxh3-stack-AWSEBRDSDBSecurityGroup-16MS5ESZRS3EO | |
2020-09-12 15:00:41 INFO Creating RDS database named: aa19ydkibpws6jb. This may take a few minutes. | |
2020-09-12 15:02:15 INFO Created load balancer named: arn:aws:elasticloadbalancing:ap-south-1:575689566956:loadbalancer/app/awseb-AWSEB-2KHZYMLZHUJA/47bcde8d23c308c5 | |
2020-09-12 15:02:15 INFO Created Load Balancer listener named: arn:aws:elasticloadbalancing:ap-south-1:575689566956:listener/app/awseb-AWSEB-2KHZYMLZHUJA/47bcde8d23c308c5/c6766141be366dbc | |
..... |
Here is the sample log once we start creating it.
You might get an error here because we haven’t set up database.yml correctly.
Step3
Add production configure like below 👇 in your database.yml file
production: | |
adapter: postgresql | |
encoding: unicode | |
database: <%= ENV['RDS_DB_NAME'] %> | |
username: <%= ENV['RDS_USERNAME'] %> | |
password: <%= ENV['RDS_PASSWORD'] %> | |
host: <%= ENV['RDS_HOSTNAME'] %> | |
port: <%= ENV['RDS_PORT'] %> | |
password: postgres |
Now, the setup environment variable
eb setenv SECRET_KEY_BASE= xxxxxx
For SECRET_KEY_BASE just fire on terminal
RAILS_ENV=production bundle exec rake secret
That’s it we are done with all configuration lets deploy our application.
eb deploy staging
If you check on AWS Elastic Beanstalk dashboard you can see our application created successfully.
Step4
Beanstalk Deploy is a GitHub action (and command-line script) to deploy apps to AWS Elastic Beanstalk. It takes the application name, environment name, version name, region and filename as parameters, uploads the file to S3, creates a new version in Elastic Beanstalk, and then deploys that version to the environment. It will wait until the deployment is finished, logging any messages from the environment during the update and exiting with a non-zero exit code if the deployment fails. It does not handle rolling back the environment.
Now let’s add this to our Github Action workflow
- name: Generate deployment package | |
run: | | |
zip -r deploy.zip . -x '*.git*' | |
- name: Deploy to EB | |
uses: einaregilsson/beanstalk-deploy@v11 | |
with: | |
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
application_name: testing-rails | |
environment_name: staging | |
version_label: 2 | |
region: ap-south-1 | |
deployment_package: deploy.zip |
Here you can see we need AWS_ACCESS_KEY and AWS_SECRET_ACCESS_KEY using GitHub secrets you can add your credentials and those environmental variables accessible in your GitHub workflow.
Now let’s push our workflow changes & let’s see the result…
Hureeeee 🎉, our code builds successfully and deployed to AWS Beanstalk.
Here is a full code snippet 👇
name: CI/CD | |
on: [push, pull_request] | |
jobs: | |
build: | |
runs-on: ubuntu-latest | |
services: | |
db: | |
image: postgres:11 | |
ports: ["5432:5432"] | |
env: | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
options: >- | |
--health-cmd pg_isready | |
--health-interval 10s | |
--health-timeout 5s | |
--health-retries 5 | |
redis: | |
image: redis | |
ports: ["6379:6379"] | |
options: --entrypoint redis-server | |
steps: | |
- uses: actions/checkout@v1 | |
- name: Setup Ruby | |
uses: actions/setup-ruby@v1 | |
with: | |
ruby-version: 2.6.x | |
- uses: borales/actions-yarn@v2.0.0 | |
with: | |
cmd: install | |
- name: Build and run test | |
env: | |
DATABASE_URL: postgres://postgres:@localhost:5432/test | |
REDIS_URL: redis://localhost:6379/0 | |
RAILS_ENV: test | |
POSTGRES_USER: postgres | |
POSTGRES_PASSWORD: postgres | |
run: | | |
sudo apt-get -yqq install libpq-dev | |
gem install bundler | |
bundle install --jobs 4 --retry 3 | |
bundle exec rake db:create | |
bundle exec rake db:migrate | |
bundle exec rspec . | |
- name: Generate deployment package | |
run: | | |
zip -r deploy.zip . -x '*.git*' | |
- name: Deploy to EB | |
uses: einaregilsson/beanstalk-deploy@v11 | |
with: | |
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
application_name: testing-rails | |
environment_name: staging | |
version_label: 3 | |
region: ap-south-1 | |
deployment_package: deploy.zip |
Conclusion
There are many tools that can help enable a smoother transition to a CI/CD process. Testing is a large part of that process because even if you are able to make your integrations and delivery faster, it would mean nothing if was done so without quality in mind. Also, the more steps of the CI/CD pipeline that can be automated, the faster quality releases can be accomplished.
At Scalereal We believe in Sharing and Open Source.
So, If you found this helpful please give some claps 👏 and share it with everyone.
Sharing is Caring!
Thank you ;)