Mở đầu
Bài này mình viết với mục đích để tổng hợp lại kiến thức và bổ sung kĩ thuật liên quan đến server cho bản thân. Thực tế thì có rất nhiều cách để deploy ứng dụng nodejs (express framework), nhưng trong bày này mình sẽ lựa chọn stack capistrano + pm2 + nginx
Init project
Để cho nhanh mình sử dụng express-generator để init project như sau:
1 2 | npx express-generator --view=ejs --git |
Tham khảo: https://expressjs.com/en/starter/generator.html .
Sau khi đã init và start ứng dụng thành công , bước tiếp theo là cài đặt capistrano cho việc tự động depoly code lên server
Capistrano (local side)
Prerequisite
Trước tiên các bạn cần phải cài đặt ruby (rbenv or rvm). Sau đó bạn có thể init Gemfile bằng câu lệnh bundle init
Edit Gemfile như dưới đây, sau đó chạy bundle install
để cài đặt gem.
1 2 3 4 5 6 7 8 9 10 | source 'https://rubygems.org' git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } group :development do gem 'capistrano', require: false gem 'capistrano-npm', require: false end |
Generate file config với câu lệnh $ cap install
. Trong Capfile
bỏ comment dòng dưới đây
1 2 3 4 5 6 7 8 9 10 | # Load DSL and set up stages require 'capistrano/setup' require 'capistrano/deploy' require 'capistrano/scm/git' install_plugin Capistrano::SCM::Git require 'capistrano/npm' require 'capistrano/slackify' Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } |
Việc tiếp theo là sửa file config/deploy.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # config valid for current version and patch releases of Capistrano lock "~> 3.14.1" set :application, 'nodejs_skeleton' set :repo_url, '<a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a>:duongpham910/nodejs_skeleton.git' set :keep_releases, 5 set :deploy_to, '/var/www/nodejs_skeleton' namespace :deploy do desc 'Restart pm2' task :restart_app do on roles(:app) do execute 'sudo /etc/init.d/nodejs_skeleton restart' end end before :finished, :restart_app end |
Tuỳ thuộc vào môi trường ( trong trường hợp này là production). Mở file config/deploy/production.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | set :user, 'ec2-user' set :stage, :production set :ssh_options, { keys: %w(~/key.pem), forward_agent: false, auth_methods: %w(publickey) } # Pass varibale to deploy from different git branches set :deploy_ref, ENV['DEPLOY_REF'] if fetch(:deploy_ref) set :branch, fetch(:deploy_ref) else set :branch, 'master' end # Setup IP with ec2 server server '', user: fetch(:user), roles: %w[app web] |
EC2 (server side)
Prerequisite
Cần cài đặt các package sau
- Nginx
1 2 | sudo yum install nginx |
- Node (& yarn or npm)
Cài đặt nvm
1 2 3 | > git clone https://github.com/creationix/nvm.git ~/.nvm source ~/.nvm/nvm.sh |
Sửa file .bashrc như dưới, sau đó chạy lệnh source .bashrc
1 2 3 4 | if [[ -s ~/.nvm/nvm.sh ]] ; then source ~/.nvm/nvm.sh ; fi |
Cuối cùng
1 2 3 4 5 | nvm install 12.16.1 nvm use 12.16.1 node -v ln -s `which node` /usr/local/bin/node //create symlink |
- PM2
1 2 3 | npm install -g pm2 ln -s `which pm2` /usr/local/bin/pm2 //create symlink |
App preparation
Các bạn cần tạo thư mục có đường dẫn như sau /var/www/nodejs_skeleton
. Cấp quyền cho thư mục:
1 2 | sudo chmod -R 777 /www |
Trong nodejs_skeleton directory tạo thư mục sau để chuẩn bị cho việc deploy bằng capistrano
1 2 3 4 | repo releases shared |
Configuration
Nginx
Tạo file config như sau sudo vi /etc/nginx/conf.d/nodejs_skeleton.conf
1 2 3 4 5 6 7 8 9 10 | server { location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } } |
Tiếp theo các bạn cần sửa file nginx.config sudo vi /etc/nginx/nginx.conf
1 2 3 4 5 | # listen 80 default_server; # listen [::]:80 default_server; # server_name _; # root /usr/share/nginx/html; |
Kiểm tra file nginx đã hoạt động chính xác chưa
1 2 3 | sudo nginx -t Still error: nginx: [warn] conflicting server name "" on 0.0.0.0:80, ignored |
Lệnh start/stop/restart service của nginx như sau
1 2 3 | sudo chkconfig nginx on sudo service nginx start/stop/restart |
PM2
Tạo thư mục client_deploy rồi tạo file config cho PM2 như dưới (hoặc đặt trong thư mục shared) với cluster 2 instance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> apps <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'nodejs_skeleton'</span><span class="token punctuation">,</span> cwd<span class="token operator">:</span> <span class="token string">'/var/www/nodejs_skeleton/current'</span><span class="token punctuation">,</span> script<span class="token operator">:</span> <span class="token string">'/var/www/nodejs_skeleton/current/bin/www'</span><span class="token punctuation">,</span> exec_mode<span class="token operator">:</span> <span class="token string">'cluster_mode'</span><span class="token punctuation">,</span> instances<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> max_memory_restart<span class="token operator">:</span> <span class="token string">'300M'</span><span class="token punctuation">,</span> env<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token constant">NODE_ENV</span><span class="token operator">:</span> <span class="token string">'production'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
Init.d Script
- Tạo 1 Script tên
nodejs_skeleton
như dưới trong/etc/init.d/nodejs_skeleton
- Cấp quyền:
sudo chmod 755 /etc/init.d/nodejs_skeleton
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | <span class="token shebang important">#!/bin/bash</span> <span class="token comment"># chkconfig: 2345 98 02</span> <span class="token comment">#</span> <span class="token comment"># description: PM2 next gen process manager for Node.js</span> <span class="token assign-left variable">NAME</span><span class="token operator">=</span>nodejs_skeleton <span class="token assign-left variable">PM2</span><span class="token operator">=</span>/usr/local/bin/pm2 <span class="token assign-left variable">NODE</span><span class="token operator">=</span>/usr/local/bin/node <span class="token assign-left variable"><span class="token environment constant">USER</span></span><span class="token operator">=</span>ec2-user <span class="token assign-left variable">CONFIG_FILE_PATH</span><span class="token operator">=</span>/home/ec2-user/client_deploy/ecosystem.config.js <span class="token builtin class-name">export</span> <span class="token assign-left variable"><span class="token environment constant">PATH</span></span><span class="token operator">=</span>/usr/local/bin:<span class="token environment constant">$PATH</span> <span class="token builtin class-name">export</span> <span class="token assign-left variable">PM2_HOME</span><span class="token operator">=</span><span class="token string">"/home/ec2-user/.pm2"</span> <span class="token function-name function">get_user_shell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">local</span> shell <span class="token assign-left variable">shell</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>getent <span class="token function">passwd</span> <span class="token string">"<span class="token variable">${1<span class="token operator">:-</span>$(whoami)}</span>"</span> <span class="token operator">|</span> <span class="token function">cut</span> -d: -f7 <span class="token operator">|</span> <span class="token function">sed</span> -e <span class="token string">'s/[[:space:]]*$//'</span><span class="token variable">)</span></span> <span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$shell</span> <span class="token operator">==</span> *<span class="token string">"/sbin/nologin"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$shell</span> <span class="token operator">==</span> <span class="token string">"/bin/false"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> -z <span class="token string">"<span class="token variable">$shell</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token assign-left variable">shell</span><span class="token operator">=</span><span class="token string">"/bin/bash"</span> <span class="token keyword">fi</span> <span class="token builtin class-name">echo</span> <span class="token string">"<span class="token variable">$shell</span>"</span> <span class="token punctuation">}</span> <span class="token function-name function">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">local</span> shell <span class="token assign-left variable">shell</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>get_user_shell <span class="token environment constant">$USER</span><span class="token variable">)</span></span> <span class="token function">su</span> - <span class="token string">"<span class="token environment constant">$USER</span>"</span> -s <span class="token string">"<span class="token variable">$shell</span>"</span> -c <span class="token string">"PATH=<span class="token environment constant">$PATH</span>; PM2_HOME=<span class="token variable">$PM2_HOME</span> <span class="token variable">$*</span>"</span> <span class="token punctuation">}</span> <span class="token function-name function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">echo</span> <span class="token string">"Starting <span class="token variable">$NAME</span>"</span> super <span class="token variable">$PM2</span> start <span class="token variable">$CONFIG_FILE_PATH</span> --update-env <span class="token punctuation">}</span> <span class="token function-name function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> super <span class="token variable">$NODE</span> <span class="token variable">$PM2</span> <span class="token function">kill</span> <span class="token punctuation">}</span> <span class="token function-name function">restart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">echo</span> <span class="token string">"Restarting <span class="token variable">$NAME</span>"</span> stop start <span class="token punctuation">}</span> <span class="token function-name function">reload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">echo</span> <span class="token string">"Reloading <span class="token variable">$NAME</span>"</span> super <span class="token variable">$PM2</span> reload all <span class="token punctuation">}</span> <span class="token function-name function">status</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">echo</span> <span class="token string">"Status for <span class="token variable">$NAME</span>:"</span> super <span class="token variable">$NODE</span> <span class="token variable">$PM2</span> list <span class="token assign-left variable">RETVAL</span><span class="token operator">=</span><span class="token variable">$?</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token keyword">in</span> start<span class="token punctuation">)</span> start <span class="token punctuation">;</span><span class="token punctuation">;</span> stop<span class="token punctuation">)</span> stop <span class="token punctuation">;</span><span class="token punctuation">;</span> status<span class="token punctuation">)</span> status <span class="token punctuation">;</span><span class="token punctuation">;</span> restart<span class="token punctuation">)</span> restart <span class="token punctuation">;</span><span class="token punctuation">;</span> *<span class="token punctuation">)</span> <span class="token builtin class-name">echo</span> <span class="token string">"Usage: {start|stop|status|restart}"</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span> <span class="token punctuation">;</span><span class="token punctuation">;</span> <span class="token keyword">esac</span> <span class="token builtin class-name">exit</span> <span class="token variable">$RETVAL</span> |
Commands
Cũng giống như các câu lệnh của webserver như nginx. Khi bị lỗi cũng có thể stop/start hoặc restart.
- Start PM2
1 2 | sudo /etc/init.d/nodejs_skeleton start |
- Stop PM2
1 2 | sudo /etc/init.d/nodejs_skeleton stop |
- Restart PM2
1 2 | sudo /etc/init.d/nodejs_skeleton restart |
- Check Status
1 2 | sudo /etc/init.d/nodejs_skeleton status |
Deploy
1 2 3 | cap staging deploy // Deploy code base from master branch cap staging deploy DEPLOY_REF=A // Deploy code from branch A |