1: Create user deploy
Suppose you were added by a customer to a server. The first step you will have to be a dedicated user to deploy.
- Create a file create_user.sh
1 2 3 4 5 6 7 8 9 10 | #!/bin/bash useradd -d /home/deploy -s /bin/bash -m deploy -G admin mkdir /home/deploy/.ssh echo '' > /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh chmod 700 -R /home/deploy/.ssh chmod 600 -R /home/deploy/.ssh/* sed -i '/^deploy:.*$/d' /etc/shadow echo 'deploy:$6$OPvierkr$8UjzyjKbwHALdZe8c.R7dsbP3eoQMtYbW.9gPhwHc2aJIiu3tyx2JlKHFDkL9sM4p6EGH./U2QBABtnxYOttK0:18158:0:99999:7:::' >> /etc/shadow |
After creating permissions for the .sh
file
1 2 | chmod +x deploy.sh |
- Decentralization is complete we will run
.sh
file
1 2 | source ./deploy.sh |
Ok so the user deploy has been created with the user name deploy
password is 12345678
(you can change the password again) then run:
1 2 3 | su - deploy nhập mật khâu: 12345678 |
2 Config server remote
2.1 Install dependencies for ruby and rails
1 2 3 4 5 6 7 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev nodejs yarn |
2.2 Installing ruby using rvm (ruby 2.5.3)
1 2 3 4 5 6 7 8 9 | sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm rvm install 2.5.3 rvm use 2.5.3 --default ruby -v gem install bundler |
2.3 Installing rails and NodeJs (rails 5.2.3)
1 2 3 4 5 6 7 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get install -y nodejs gem install rails -v 5.2.3 rails -v # Rails 5.2.3 |
2.4 Install Mysql
1 2 | sudo apt-get install mysql-server mysql-client libmysqlclient-dev |
You can specify the version of mysql 5.7 as below and the same with other versions.
1 2 | apt-get install mysql-server-5.7 mysql-clients-5.7 mysql-server-cores-5.7 |
2.5 Config nginx
1 2 | sudo apt-get install nginx |
Because I use unicorn, we need an nginx config file to point to unicorn
You create the file nginx_app_rails
as follows
1 2 | vi /etc/nginx/site-available/nginx_app_rails |
And fix that file as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | upstream backend-unicorn { server unix:/usr/local/rails_apps/<PROJECT_NAME>/current/tmp/sockets/unicorn.sock fail_timeout=0; } server { listen 80; #server_name <'domain'>; server_name 192.168.xxx.xx; client_max_body_size 10M; root /usr/local/rails_apps/<PROJECT_NAME>/current/public; location ~ ^/.*.(gif|ico|css|js)$ { expires 3d; break; } location / { try_files $uri @webapp; } location @webapp { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 3000; proxy_pass http://backend-unicorn; } } |
In the file above you pay attention to the following 3:
1 2 3 4 | upstream backend-unicorn { server unix:/usr/local/rails_apps/<PROJECT_NAME>/current/tmp/sockets/unicorn.sock fail_timeout=0; } |
The server unix
line must point to the unicorn.sock file of the code deploy test (the current directory)
1 2 3 | server_name 192.168.xxx.xx; #server_name <'domain'>; |
This will configure the remote server’s domain and ip address
1 2 | root /usr/local/rails_apps/<PROJECT_NAME>/current/public; |
This will point to the code deploy that was generated when we ran the command
1 2 | cap staging deploy |
After all we will enable it on:
1 2 | sudo ln -s /etc/nginx/sites-available/nginx_app_rails /etc/nginx/sites-enabled/nginx_app_rails |
3 Config server local and add gem to config
3.1 Config unicorn
Add the following gems to the gem file
1 2 3 4 | group :staging, :production do gem "unicorn" end |
Create config/unicorn.rb
and edit it as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # set path to the application app_dir git File.expand_path("../..", __FILE__) shared_dir = "#{app_dir}/shared" working_directory app_dir # Set unicorn options worker_processes 2 preload_app true timeout 30 # Path for the Unicorn socket listen "#{shared_dir}/sockets/unicorn.sock", backlog: 128 # Set path for logging stderr_path "#{shared_dir}/log/unicorn.stderr.log" stdout_path "#{shared_dir}/log/unicorn.stdout.log" # Set proccess id path pid "#{shared_dir}/pids/unicorn.pid" |
Next, we will configure staging and production
- Create a folder with the following format:
1 2 3 4 5 6 | project_name - config - unicorn - staging.rb - production.rb |
And then corrected as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #config/unicorn/staging.rb worker_processes 2 app_directory = "/usr/local/rails_apps/<PROJECT_NAME>/current" working_directory app_directory listen "#{app_directory}/tmp/sockets/unicorn.sock", backlog: 128 timeout 3000 pid "#{app_directory}/tmp/pids/unicorn.pid" stderr_path "#{app_directory}/log/unicorn_staging.log" stdout_path "#{app_directory}/log/unicorn_staging.log" preload_app true before_exec do |server| ENV["BUNDLE_GEMFILE"] = "#{app_directory}/Gemfile" end before_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end old_pid = "#{server.config[:pid]}.oldbin" if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH end end end after_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end end |
As above file we need to understand some lines as follows:
- worker_processes: 2 # like this unicorn will run with 2 processes
- app_directory = “…..” the current unencrypted directory is currently deploying on the generated system when we run up to
cap staging deploy
- listen “# {app_directory} /tmp/sockets/unicorn.sock”, backlog: 128 # Listening to unicorn.sock I don’t really understand this
- pid “…” points to the unicorn pid when the project runs unicorn will have a unicord pid generated
- stderr_path and stdout_path where unicorn logs are stored
- preload_app true # I don’t know what this is, kind of reload the app to get the configs above I guess =))
- As for the code below, the check section when I fork the github code, check if the activerecord is defined as having not deleted the app’s pid if it exists. and finally check if the connection is successful with the DB (
ActiveRecord::Base.establish_connection
)
3.2 Config capistrino
Ok, now that we have finished configuring unicorn, we will now use Capistrano
to auto load the rails app’s dependencies.
We use some of the following gems:
1 2 3 4 5 6 7 8 | # deployment gem "capistrano", "~> 3.4.1" gem "capistrano3-unicorn" gem "rvm1-capistrano3", require: false gem "capistrano-rails" gem "capistrano-sidekiq" gem "capistrano-bundler" |
and comment the gem 'puma'
again. and then bundle
1 2 | bundle install |
After bundle we run the following command to create the file. bundle exec cap install
Will generate the following files:
1 2 3 4 5 6 7 8 | mkdir -p config/deploy create config/deploy.rb create config/deploy/staging.rb create config/deploy/production.rb mkdir -p lib/capistrano/tasks create Capfile Capified |
Ok then they will go config: 1: config Capfile
1 2 3 4 5 6 7 8 9 10 11 12 13 | #Capfile require "capistrano/setup" require "capistrano/deploy" require "capistrano3/unicorn" require "rvm1/capistrano3" require "capistrano/bundler" require "capistrano/rails" require "capistrano/rails/assets" require "capistrano/rails/migrations" Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } |
Done config file config/deploy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | lock "3.4.1" set :application, "<PROJECT_NAME>" set :repo_url, " <a class="__cf_email__" href="/cdn-cgi/l/email-protection" data-cfemail="bddad4c9fddad4c9d5c8df93ded2d0">[email protected]</a> :thang/PROJECT_NAME.git" set :deploy_to, "/usr/local/rails_apps/PROJECT_NAME" set :default_stage, "development" ask :branch, proc {`git rev-parse --abbrev-ref HEAD`.chomp}.call set :deploy_via, :remote_cache set :format, :pretty set :log_level, :debug set :keep_releases, 5 set :rvm_type, :system set :rvm1_ruby_version, "2.5.3" set :linked_files, %w{config/database.yml config/master_database.yml} set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle} set :scm, :git set :pid_file, "#{shared_path}/tmp/pids/unicorn.pid" namespace :deploy do desc "Restart application" task :restart do on roles(:app), in: :sequence, wait: 5 do invoke "unicorn:restart" end end after :publishing, :restart desc "Upload database.yml" task :upload do on roles(:app) do |host| if test "[ ! -d #{shared_path}/config ]" execute "mkdir -p #{shared_path}/config" end upload!("config/database.yml", "#{shared_path}/config/database.yml") upload!("config/enfit_master_database.yml", "#{shared_path}/config/master_database.yml") end end before :starting, "deploy:upload" end |
Next config for production and staging in config/deploy
1 2 3 4 5 6 7 8 9 10 11 12 | #config/deploy/staging.rb set :stage, :production set :rails_env, :production server ENV["API_APP_SERVER"], user: "deploy", roles: %w{app} set :ssh_options, { keys: %w(/home/deploy/.ssh/id_rsa), forward_agent: true, } |
1 2 3 4 5 6 7 8 9 10 11 | #config/deploy/production.rb set :stage, :staging set :rails_env, :staging server ENV["API_APP_SERVER"], user: "deploy", roles: %w{app} set :ssh_options, { keys: %w(/home/deploy/.ssh/id_rsa), forward_agent: true, } |
But things to notice in the above file are:
1 2 | ENV["API_APP_SERVER"]: là địa chỉ của server remote |
Ok so basically the next config is but the processing process.
4 Some other things
1: Create ssh key for user deploy
1 2 3 4 5 | ssh-keygen -t rsa -b 4096 # Add key to the ssh-agent eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa |
Done when ssh key we add ssh to github account
2 Create a .env
file
1 2 3 4 5 6 | cd home/deploy vi .env Và sửa file như sau: export API_APP_SERVER="xxxxxx" export SECRET_KEY_BASE="xxxxxxxx" |
NOTE: The ~/.bashrc
file must be edited again to receive the .env
file
1 2 3 4 | if [ -f ~/.env ]; then . ~/.env fi |
Finally: source .env
and source .bashrc
to get environment variables
3 clone code from github
1 2 3 | cd home/deploy git clone xxxx |
4 Run rails commands when clone project returns
1 2 3 4 | bundle install rake db:create rake db:migrate |
5 Deploy
1 2 | cap staging deploy |
When running this command we will get some errors as follows:
- Not authorized for rails_app directory
1 2 3 4 5 6 | chmod 757 rails_app Trong đó 7: user sở hữu thư mục rails_app (user deploy) full quyen (read write execute) 5: Nhưng thằng user thuộc group của user deploy có quyền 5 (quyền read and execute) 7: Nhưng thằng user không có thuộc group của user deploy sẽ có full quyênd |
- Database file has not been created
1 2 3 4 | cd home/deloy/project_name vi project_name/config/database.yml và edit nó |
- Have not created the
configscrets.yml
file This file has cproduction: How to create the key screts
1 2 3 4 | bundle exec rake secret hoặc bundle exec rake -T |
1 2 3 | staging: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>ó nội dung như sau: |
Those are some of the errors I encountered, but the fact that when you run uncort, there is an error in the following directory to find the error
1 2 3 | cd /usr/local/rails_apps/enFIT_API_APP/current tail -100f log/unicorn/unicorn.log |
- Create credentials file When an error is:
1 2 3 | bundler: failed to load command: unicorn (/usr/local/rails_apps/THANG_APP/shared/bundle/ruby/2.5.0/bin/unicorn) ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `rails credentials:edit` |
Then we need:
1 2 | EDITOR="vim" bin/rails credentials:edit |
NOTE:
Remember to remove the master.key file and credentials before running the command