This is my first article creation. I was able to deploy the method for deploying to EC2 with CircleCi + Capistrano by referring to other articles, so I summarized the contents as an article creation practice.
--CircleCi GUI is as of the time of writing (January 19, 2021). --Deployment to EC2 instance is completed locally with Capistrano. --RSpec and RuboCop already work on CircleCi. (If you haven't done so already, here was helpful.)
Note: This article is for an SSH connection to an EC2 instance with any IP address. (We will add it as soon as it is supported.)
First, change the format of the SSH key to register with CircleCi. You probably created it to access your EC2 instance in the past, referring to the article here. To change the format of your SSH key (~/.ssh/your_app_key_rsa), do the following:
$ chmod 700 ~/.ssh/your_app_key_rsa
$ ssh-keygen -p -m PEM -f ~/.ssh/your_app_key_rsa
$ chmod 400 ~/.ssh/your_app_key_rsa
$ cat ~/.ssh/your_app_key_rsa #Register the contents of the file displayed by this command in CircleCi.
It is OK if the contents of the file are "BEGIN RSA PRIVATE KEY" as shown below. -----BEGIN RSA PRIVATE KEY----- A list of letters including numbers -----END RSA PRIVATE KEY-----
Reference: [AWS] Resolution of errors addicted to CircleCI/CD automatic deployment [Capistrano]
Next, perform the following operations to register the SSH key whose format has been changed to CircleCi.
1.Click "Project Settings" at the top right of the screen of the repository page registered with CircleCi
2.Select "SSH KEYS" on the left side of the screen.
3.Select "Add SSH Key" to the right of the "Additional SSH Keys" item at the bottom of the screen.
4.Enter the "Elastic IP" assigned to the EC2 instance in the "Host name" of the displayed item.
5.「Private Key*To ""~/.ssh/your_app_key_Copy and paste the contents of "rsa".
6."Hostname" and "Private Key"*After entering, register the SSH key with "Add SSH Key".
In 5, please copy not only the enumeration of numbers and letters but also the following. -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- When registration is completed in 6, "Elastic IP" will be added to "Hostname" in the "Additional SSH Keys" item. The Fingerprint should say something like "12:34:56: ab ...". The displayed "Fingerprint" will be used to register the "environment variables" that will be performed later. (Environment variables do not include ":".)
When the file is generated, it is generated as ~/.ssh/id_rsa_ followed by Fingerprint without:. If you use the registered SSH key, it will be automatically added to "ssh-agent". Even if you are using "ssh-agent" to connect to GitHub from your server environment, you only need to register one SSH key to use to connect to your EC2 instance locally.
Then add an environment variable to Circleci.
1.Current screen("SSH KEYS screen")Select "Environment Variables" to the left of.
2.Then select Add Environment Variable.
3.An item similar to the SSH key registration will appear, so "Name"*As a variable name in "PRODUCTION"_SSH_Enter "KEY".
4. 「Value*To ""~/.ssh/id_rsa_Like "123456abcd"~/.ssh/id_rsa_I registered behind
From the contents of "Fingerprint" of the SSH key, ":"Is omitted.
5.Register the environment variable with "Add Environment Variable".
Even if multiple SSH keys are registered in CircleCi, they are saved as the file name of "id_rsa_Fingerprint: omitted" under "~/.ssh", so the path to the SSH key is registered as an environment variable. If you want to check, please refer to the official Debugging with SSH.
It's just a change of keys :, so it's done right away.
config/deploy/production.rb
server Rails.application.credentials.dig(:amazon, :ec2_ip),
user: Rails.application.credentials.dig(:amazon, :ec2_user),
roles: %w[web app db]
set :ssh_options, {
keys: (ENV['PRODUCTION_SSH_KEY']), #Edit only here
forward_agent: true
}
I edited it for myself by referring to the contents of Running SystemSpec (RSpec) and Rubocop on CircleCI.
yml:.circleci/config.yml(Before editing)
version: 2.1
orbs:
ruby: circleci/[email protected]
jobs:
build:
docker:
- image: circleci/ruby:2.7.2-node
environment:
- BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
test:
parallelism: 3
docker:
- image: circleci/ruby:2.7.2-node
environment:
- DB_HOST: 127.0.0.1
- RAILS_ENV: test
- BUNDLER_VERSION: 2.1.4
- image: circleci/mysql:8.0
command: |
mysqld --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_ROOT_PASSWORD: password
- MYSQL_USER: root
- MYSQL_ROOT_HOST: '%'
- image: selenium/standalone-chrome:latest
name: chrome
steps:
- checkout
- ruby/install-deps
- run: mv config/database.yml.ci config/database.yml
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:3306 -timeout 1m
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
# Run rspec in parallel
- ruby/rubocop-check
- ruby/rspec-test
workflows:
version: 2
build_and_test:
jobs:
- build
- test:
requires:
- build
Add the following part to this by referring to I tried to automatically deploy with CircleCI + Capistrano + AWS (EC2) + Rails.
yml:.circleci/config.yml(Additional points)
#abridgement
deploy:
docker:
- image: circleci/ruby:2.7.2-node
environment:
BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
- add_ssh_keys:
fingerprints:"XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
- deploy:
name: Capistrano deploy
command: bundle exec cap production deploy
workflows:
version: 2
build_accept_deploy:
jobs:
- build
- test:
requires:
- build
- deploy:
requires:
- test
filters:
branches:
only: master
yml:.circleci/config.yml(Overall)
version: 2.1
orbs:
ruby: circleci/[email protected]
jobs:
build:
docker:
- image: circleci/ruby:2.7.2-node
environment:
- BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
test:
parallelism: 3
docker:
- image: circleci/ruby:2.7.2-node
environment:
- DB_HOST: 127.0.0.1
- RAILS_ENV: test
- BUNDLER_VERSION: 2.1.4
- image: circleci/mysql:8.0
command: |
mysqld --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_ROOT_PASSWORD: password
- MYSQL_USER: root
- MYSQL_ROOT_HOST: '%'
- image: selenium/standalone-chrome:latest
name: chrome
steps:
- checkout
- ruby/install-deps
- run: mv config/database.yml.ci config/database.yml
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:3306 -timeout 1m
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
# Run rspec in parallel
- ruby/rubocop-check
- ruby/rspec-test
deploy:
docker:
- image: circleci/ruby:2.7.2-node
environment:
BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
- add_ssh_keys:
fingerprints: "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
- deploy:
name: Capistrano deploy
command: bundle exec cap production deploy
workflows:
version: 2
build_accept_deploy:
jobs:
- build
- test:
requires:
- build
- deploy:
requires:
- test
filters:
branches:
only: master
- add_ssh_keys:
fingerprints: "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
The part of
1.Click "Project Settings" at the top right of the screen of the repository page registered with CircleCi
2.Select "SSH KEYS" on the left side of the screen.
3.There is an SSH key added at the bottom of the screen, so Fingerprint (12):34:56:ab:cd:ef:...Like)
Copy and paste.
Since the IP address used by CircleCi is not always the same, it is not possible to deploy if the security group limits the IP address that can be SSH connected. (Can be deployed if not limited) The next step is to address this security issue.
Reference: Run SystemSpec (RSpec) and Rubocop on CircleCI I tried automatic deployment with CircleCI + Capistrano + AWS (EC2) + Rails
Next, we will make it possible to deploy only with the IP address (My IP) limited by the security group and the IP address used on CircleCi.
First, create an IAM user by referring to this article and be sure to download the CSV (to register the contents of the CSV as an environment variable in CircleCi). Then do the following:
1.Select "EC2" from the service.
2.Select "Security Group" from the EC2 dashboard.
3.Select "Create Security Group" at the top right of the screen.
4."CircleCi" in "Security Group Name" and "Description"-Enter "Security Group".
5.Select the VPC that has the deployment destination instance in "VPC".
6.Create by selecting "Create Security Group".
7.Select "Instance" from the EC2 dashboard.
8.Select "Change Security Group" from "Action" of the instance to deploy to
9.Add the created security group to "Associated Security Group".
This completes the settings on AWS.
There is no need to add inbound rules when creating a security group. The ID of the created security group will be registered in CircleCi later as an environment variable.
CircleCI adds the following four as environment variables. 「AWS_ACCESS_KEY_ID」「AWS_SECRET_ACCESS_KEY」「AWS_DEFAULT_REGION」「SECURITY_GROUP_ID」
Register the contents of the CSV downloaded earlier with the following names.
Name to "AWS_ACCESS_KEY_Register the value of "Access key ID" in "ID" and Value.
Name to "AWS_SECRET_ACCESS_Register the values of "KEY" and "Secret access key".
Register the remaining two environment variables.
Name to "AWS_DEFAULT_"REGION", Value to "ap"-northeast-Enter "1" to register.
Name is "SECURITY"_GROUP_Register "ID of the security group created earlier" in "ID" and Value.
This completes the settings on CircleCi.
I think that the value of "AWS_DEFAULT_REGION" is basically "ap-northeast-1", but if it is different, please register an appropriate region name.
Reference: I want to allow a specific IP only when accessing EC2 from CircleCI 2.0
Now let's edit the configuration file. (At the very end there is the entire source code)
yml:.circleci/config.yml(changes)
orbs:
ruby: circleci/[email protected]
aws-cli: circleci/[email protected] #add to
jobs:
#abridgement
deploy:
docker:
- image: circleci/ruby:2.7.2-node
environment:
BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
- aws-cli/install #add to
- add_ssh_keys:
fingerprints: "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
- deploy:
name: Capistrano deploy
command: ./deploy.sh #Changed to run shell script
Next, I created a shell script with the content "deploy.sh" based on this article. (The content has been edited a little with reference to this article.)
deploy.sh
#!/bin/sh
set -ex
IP=`curl -f -s ifconfig.me`
trap "aws ec2 revoke-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32
bundle exec cap production deploy
Finally, change the authority of "deploy.sh" with the following command.
$ chmod +x deploy.sh
You can now deploy from CircleCi to an EC2 instance, but as a confirmation, the IP address of CircleCi is added to the security group created at the time of deployment, and the added IP address is deleted after the deployment is completed or when it fails. Please confirm that.
Reference: Story of deploying from CircleCI Deploy from CircleCI to AWS (EC2) using Capistrano
I will put the source code according to the predecessor.
yml:.circleci/config.yml
version: 2.1
orbs:
ruby: circleci/[email protected]
aws-cli: circleci/[email protected]
jobs:
build:
docker:
- image: circleci/ruby:2.7.2-node
environment:
- BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
test:
parallelism: 3
docker:
- image: circleci/ruby:2.7.2-node
environment:
- DB_HOST: 127.0.0.1
- RAILS_ENV: test
- BUNDLER_VERSION: 2.1.4
- image: circleci/mysql:8.0
command: |
mysqld --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_ROOT_PASSWORD: password
- MYSQL_USER: root
- MYSQL_ROOT_HOST: '%'
- image: selenium/standalone-chrome:latest
name: chrome
steps:
- checkout
- ruby/install-deps
- run: mv config/database.yml.ci config/database.yml
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:3306 -timeout 1m
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
# Run rspec in parallel
- ruby/rubocop-check
- ruby/rspec-test
deploy:
docker:
- image: circleci/ruby:2.7.2-node
environment:
BUNDLER_VERSION: 2.1.4
steps:
- checkout
- ruby/install-deps
- aws-cli/install
- add_ssh_keys:
fingerprints: "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
- deploy:
name: Capistrano deploy
command: ./deploy.sh
workflows:
version: 2
build_accept_deploy:
jobs:
- build
- test:
requires:
- build
- deploy:
requires:
- test
filters:
branches:
only: master
deploy.sh
#!/bin/sh
set -ex
IP=`curl -f -s ifconfig.me`
trap "aws ec2 revoke-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr ${IP}/32
bundle exec cap production deploy
config/deploy/production.rb
server 'Elastic IP',
user: 'username',
roles: %w[web app db]
set :ssh_options, {
keys: (ENV['PRODUCTION_SSH_KEY']),
forward_agent: true
}
config/deploy.rb
# config/credentials.yml.Make the contents of enc available
require File.expand_path('./environment', __dir__)
# config valid for current version and patch releases of Capistrano
lock '~> 3.15.0'
#Application name
set :application, 'app_name'
#github url. Specify the git hosting destination for your project
set :repo_url, '[email protected]:user_name/app_name.git'
#The directory of the server to deploy to. Specify with full path
set :deploy_to, '/var/www/rails/app_name'
#Specify Ruby version
set :rbenv_ruby, '2.7.2'
#Specify a symbolic link file, specifically a file that goes into shared
append :linked_files, 'config/master.key'
#Generate a directory of symbolic links
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets'
#Required when doing sudo etc. in a task
set :pty, true
#Number of versions to keep(* See below)
set :keep_releases, 3
#The level of the log to output.
set :log_level, :debug
# puma
set :puma_init_active_record, true
#Fixed Nginx config file name and location
set :nginx_sites_enabled_path, '/etc/nginx/conf.d'
set :nginx_config_name, "#{fetch(:application)}.conf"
Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.2'
gem 'bcrypt'
gem 'bootsnap', require: false
gem 'bootstrap'
gem 'devise'
gem 'devise-i18n'
gem 'image_processing'
gem 'jbuilder'
gem 'jquery-rails'
gem 'kaminari'
gem 'kaminari-bootstrap'
gem 'mini_magick'
gem 'mysql2'
gem 'puma', '< 5'
gem 'rails', '~> 6.0.3', '>= 6.0.3.4'
gem 'rails-i18n'
gem 'sassc-rails'
gem 'turbolinks'
gem 'uglifier'
group :development, :test do
gem 'byebug', platforms: %i[mri mingw x64_mingw]
gem 'factory_bot_rails'
gem 'rspec-rails'
end
group :development do
gem 'bcrypt_pbkdf'
gem 'capistrano'
gem 'capistrano3-puma', '< 5'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano-rbenv'
gem 'capistrano-rbenv-vars'
gem 'ed25519'
gem 'listen'
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec'
gem 'spring'
gem 'spring-watcher-listen'
gem 'sshkit-sudo'
gem 'web-console'
end
group :test do
gem 'capybara'
gem 'capybara-email'
gem 'database_cleaner'
gem 'rspec_junit_formatter'
gem 'selenium-webdriver'
end
group :production do
gem 'aws-sdk-s3', require: false
end
Recommended Posts