Auto deploy Node.js app lên server qua SSH với GitLab CI/CD và PM2
Chào các bạn, nếu các bạn chưa biết tới GitLab thì có thể tham khảo bài viết Tìm hiểu về Gitlab của tác giả Le Thi Ngoc Anh trên Viblo. Cũng giống như Github, GitLab là nơi giúp bạn có thể lưu trữ mã nguồn của mình free và private. Tuy nhiên, bạn có thể mua các gói dịch vụ khác phù hợp hơn ...
Chào các bạn, nếu các bạn chưa biết tới GitLab thì có thể tham khảo bài viết Tìm hiểu về Gitlab của tác giả Le Thi Ngoc Anh trên Viblo.
Cũng giống như Github, GitLab là nơi giúp bạn có thể lưu trữ mã nguồn của mình free và private. Tuy nhiên, bạn có thể mua các gói dịch vụ khác phù hợp hơn với nhu cầu của bạn.
Tổng quan
Bài viết hôm nay sẽ hướng dẫn Step by step thực hiện implement GitLab CI/CD để tự động build, test và deploy ứng dụng Node.js lên server thông qua SSH khi bạn push code lên GitLab. Trong khôn khổ nội dung, mình thực hiện trên:
- Ubuntu 16.04 cả server và local của mình.
- GitLab CI/CD - GitLab Runner 10, tất nhiên source code lưu trên GitLab luôn.
- Google Cloud VPS: Mình rất thích thằng này, tuy nhiên giá hơi đắt. Giá bây giờ là $32/month cho một VPS Singapore v1CPU - 10 GB SSD disk - Ubuntu 16.04. Bạn được tặng $300 lần đầu sử dụng, có thể tham khảo bài viết Vọc VPS với $$300 miễn phí từ Google của mình.
Pipeline, Job và Stage
Khi bạn push code lên GitLab, nếu bạn có GitLab CI/CD thì GitLab sẽ sinh ra 1 pipeline là tập hợp một nhóm các jobs (công việc) cần phân chia thực hiện theo các giai đoạn - Stage. Các Jobs thì sẽ được GitLab thực hiện lần lượt theo từng Stage.
VD: Khi push code lên Gitlab, chúng ta cần thực hiện test code rồi deploy nếu mọi thứ ổn. Chúng ta có có thể define ra 2 stage là: Test và Deploy. Các jobs trong stage Test cần được thực hiện trước, nếu có lỗi, test is failed thì sẽ không chạy stage Deploy.
Các bước thực hiện
- Tạo một Node.js app đơn giản.
- Trên local, tạo một SSH Key để phục vụ cho việc deploy.
- Add SSH private key vào GitLab CI/CD.
- Add SSH public key vào danh sách được phép mở kết nối ssh tới server. Để ta có thể thực hiện connect tới server qua SSH.
- Test thử dưới local, đảm bảo có thể connect tới server bằng SSH key này.
- Config GitLab CI/CD qua file: .gitlab-ci.yml gồm 2 stage:
- Test stage
- Deploy stage
Tạo một Node.js app đơn giản
Chúng ta sử dụng express luôn ha.
npm init npm install express --save
Tạo file index.js:
var express = require('express') var app = express() // respond with "hello world" when a GET request is made to the homepage app.get('/', (req, res) => res.send('Hello world!')) app.listen(3000, () => console.log('Node.js app listening on port 3000!'))
Tạo SSH key
Sử dụng luôn ssh-keygen theo hướng dẫn của Github nha:
- Mở terminal
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
Nó sẽ tạo mới một SSH key, sử dụng email được cung cấp. Tạo ra cặp public/private rsa.
- Trả lời thêm một số câu hỏi:
# Nơi lưu ssh key, các bạn nhập luôn tên file theo mình: gitlab Enter a file in which to save the key (/home/you/.ssh/id_rsa): [Press enter] # Passphrase, các bạn để trống nha: Enter passphrase (empty for no passphrase): [Type a passphrase] Enter same passphrase again: [Type passphrase again]
Hai file gitlab và gitlab.pub được tạo ra. Trong đó gitlab.pub là public key, còn gitlab là private key.
Thêm SSH public key cho Server
Copy toàn bộ nội dung của public key gitlab.pub để thêm vào server.
- Với Google Cloud server, bạn vào edit instance của bạn, kéo xuống dưới, có chỗ dán public key cho bạn như hình:
- Với server khác, cũng có cách tương tự để add ssh key hoặc bạn tự add public key vào server thủ công bằng cách dán public key vào cuối file ~/.ssh/authorized_keys.
Thêm SSH private key cho GitLab CI/CD
- Copy toàn bộ nội dung trong file private key: gitlab hồi nãy.
- Vào trong respository của trên GitLab chọn: Settings > CI/CD: Trong trang mới, chọn Secret variables để tạo biến lưu private key với tên biến là SSH_PRIVATE_KEY như trong hình.
Make sure SSH key có thể hoạt động
Dưới local bạn thử tạo connect ssh tới server dùng ssh key gitlab.
ssh -i /path/to/private_key/gitlab -o StrictHostKeyChecking=no <username>@<hostname>
Cấu hình GitLab CI/CD
- Do là Node.js app nên mình build ci/cd với docker image: node:latest.
- Define hai stage là test và deploy
- Tạo một job cho stage test, thực hiện build thử app, test eslint, code convention, unit test...
- Tạo một job cho stage deploy thực hiện deploy code lên server nếu stage test không bị fail.
Nội dung file như .gitlab-ci.yml như sau:
image: node:latest stages: - test - deploy test: stage: test environment: name: testing script: - yarn install - yarn lint - yarn build deploy-production: stage: deploy environment: name: deploying url: https://example.com only: - develop before_script: # Run ssh-agent in background: - eval "$(ssh-agent -s)" # Add SSH Key: - echo "$SSH_PRIVATE_KEY" | tr -d ' ' | ssh-add - > /dev/null - ssh-add -l # Install pm2: - npm i -g pm2 script: - pm2 -v - pm2 deploy ecosystem.config.json production
Lưu ý:
- File config này theo cú pháp yml các bạn có thể đọc thêm về loại file này ở đây: Official YAML 1.2 Documentation.
- Mỗi job test, deploy-production được thực hiện lần lượt theo từng stage được khai báo trong stages.
- Mỗi job, cần có script - chứa mảng các câu lệnh được thực hiện cho job đó, stage chỉ ra Stage mà job đó sẽ được thực hiện.
PM2 - thực hiện ssh tới server và deploy
Các bạn tạo file config PM2 như sau:
{ "apps": [ { "name": "web-app", "script": "server/index.js", "cwd": "/deploy/demo/web-app/current", "error_file": "/deploy/demo/web-app/logs/web.err.log", "out_file": "/deploy/demo/web-app/logs/web.out.log", "watch": false, "exec_mode": "cluster", "env": { "NODE_ENV": "development" }, "env_production": { "NODE_ENV": "production" } } ], "deploy": { "production": { "user": "deploy", "host": [ "example.com" // Hostname or Server IP Address ], "ref": "origin/develop", "repo": "git@gitlab.com:web-app/web-app.git", "path": "/deploy/demo/web-app", "post-setup": "yarn install; yarn build; pm2 start ecosystem.config.json --env production", "post-deploy": "yarn install; yarn build; pm2 restart ecosystem.config.json --env production", "ssh_options": [ "StrictHostKeyChecking=no", "PasswordAuthentication=no" ] } } }
Trong config có đoạn post-deploy, là danh sách các command sẽ được thực hiện trên server sau khi đã kết nối ssh. Tương ứng với thực hiện danh sách câu lệnh sau trên server:
- yarn install
- yarn build
- pm2 restart ecosystem.config.json --env production
Để hiểu thêm về PM2 bạn có thể đọc thêm bài viết Tổng quan về PM2 của mình, đồng thời đọc hiểu thêm về ý nghĩa của các pm2 config tại đây.
Tạo commit push thử
Danh sách pipeline trên GitLab sẽ hiển thị như ảnh dưới, toàn bộ stage xanh tức là toàn bộ đều success, x đỏ là stage đó chứa job bị failed:
Bạn muốn xem chi tiết các script được thực hiện như nào, click vào các icon ở mục stages để chọn. Lúc này, log sẽ hiển thị realtime ra cho bạn xem chi tiết:
# Câu lệnh thực hiện deploy lên server thông qua PM2: $ pm2 deploy ecosystem.config.json production ------------- __/\\____/\____________/\____/\\_____ _//////////\_/\________/\__////////\___ _/\_______/\_///\____///\_///______//\__ _/\/__//////_/\___________//___ _//////////____/\__////___/\________///_____ _/\_____________/\____///_____/\_____///________ _/\_____________/\_____________/\___//___________ _/\_____________/\_____________/\__/\\_ _///______________///______________///__///////////////__ ....................................................... Hash: d7f86780fb27fcb4b0ed Version: webpack 3.12.0 Time: 3239ms Asset Size Chunks Chunk Names server-bundle.json 358 kB [emitted] 2018-05-27T09:11:17.702Z nuxt:build Building done Done in 31.77s. [PM2] Applying action restartProcessId on app [web-app](ids: 0) [PM2] [web-app](0) ✓ ┌──────────┬────┬─────────┬───────┬────────┬─────────┬────────┬─────┬───────────┬───────────────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ ├──────────┼────┼─────────┼───────┼────────┼─────────┼────────┼─────┼───────────┼───────────────┼──────────┤ │ web-app │ 0 │ cluster │ 10688 │ online │ 1 │ 0s │ 74% │ 29.1 MB │ deploy │ disabled │ └──────────┴────┴─────────┴───────┴────────┴─────────┴────────┴─────┴───────────┴───────────────┴──────────┘ Use `pm2 show <id|name>` to get more details about an app ○ hook test ○ successfully deployed origin/develop --> Success Job succeeded
Update Oops!!! Mình xin phép bổ sung thêm một chút. Nếu các bạn làm theo hướng dẫn bên trên mà sau khi thực hiện deploy bị lỗi:
"..../web-app/source": No such file or directory Fetch failed
Đừng lo, lỗi trên nguyên nhân là do mình thiếu một bước setup pm2, dẫn tới thiếu thư mục. Các bạn thực hiện setup cho pm2 trước khi deploy nhé! Bằng cách chạy lệnh:
pm2 deploy production setup
Sau khi setup, pm2 sẽ tự động tạo ra một cấu trúc thư mục "Capistrano-like structure":
project_root ├── current -> releases/20150301100000 # this is a symlink to the current release └── source ├── ...code here
Hit a thank to Khoa!
Tổng kết
Như vậy mình đã hoàn thành bài viết hướng dẫn implement GitLab CI/CD, auto deploy lên server qua SSH với PM2.
Tài liêu tham khảo:
- Express "Hello world" example
- Generating a new ssh key
- Official YAML 1.2 Documentation
- PM2 - Process File documentation