Kalan's Blog

Kalan 頭像照片,在淡水拍攝,淺藍背景

四零二曜日電子報上線啦!訂閱訂起來

Software Engineer / Taiwanese / Life in Fukuoka
This blog supports RSS feed (all content), you can click RSS icon or setup through third-party service. If there are special styles such as code syntax in the technical article, it is still recommended to browse to the original website for the best experience.

Current Theme light

我會把一些不成文的筆記或是最近的生活雜感放在短筆記,如果有興趣的話可以來看看唷!

Please notice that currenly most of posts are translated by AI automatically and might contain lots of confusion. I'll gradually translate the post ASAP

Rails app automation - hubot vs heaven

前言

目前所在的公司裡頭是直接在本地端的 terminal 跑 cap staging deploy 指令。 capistrano 作為自動部署化的工具非常方便,但難免會遇到幾個問題:

  • 不是團隊中的每個人都有相同的環境
  • 大家都在部署,結果 staging 上現在到底是哪個 branch,完全一頭霧水。
  • deploy 這件事情卡在本地端。

對一家新創來說,越穩定的開發效率和流程,就越能夠專注在產品當中。所以我們希望做到幾件事:

  • 開發團隊都可以輕鬆的部署
  • 不用在本地端下指令部署,還要多設定 ssh。
  • 就算沒有開著電腦,也可以輕鬆地部署
  • 能夠記錄部署的狀況
  • 如果出問題了,可以快速 rollback 回上一個版本

逐漸厭倦了在 terminal 打指令,ssh key 手動加的日子。於是打算自己研究有沒有更流暢的部署流程。

之前在 Sudo 裡頭,幸好有 @ocowchun@henry 兩位懶工,devops 做得非常完整,才能夠專注在開發功能,而不是一堆繁複的設定當中。(雖然才剛開發完就關閉服務了...)

目前覺得最合適的解決方案是搭配 hubot-deploy 以及 heaven 來幫助部署。

但 heaven 的文件實在寫的有夠爛。

看了老半天,甚至看了一下 source code 才知道到底該怎麼設定。於是決定將整個設定流程分享給大家,希望能夠減少其他 devops 們走歪路的時間。

主要流程

{% asset_img "process.png" "Github deployment process" %}

hubot 接收到部署指令後,會發送 github deployment,同時會觸發 deployment 這個事件,這時 github 就會發送 POST 給在 webhook 設置的 url(這邊接收者為 heaven),heaven 接收到請求之後,就會開始部署,再一一回傳我們想要知道的部數狀況。

hubot-deploy

hubot-deploy 能夠用 slack 對 slack-bot 下指令的方式建立 github 的 deployment event。

heaven

是一個 Rails 的 application。主要有一個 /events 負責接收從 github deployment 傳來的 deployment 與 payload。

設定步驟

heaven 的文件寫得不明所以hubot-deploy 也是草草帶過。幾乎只能靠著他們提供的流程圖,不斷的試錯與通靈。

設定 hubot-deploy

  1. 利用 yeoman 產生 hubot,並且選擇 adapterslack

  2. package.json 中加入 hubot-deploy,或者 run npm install hubot-deploy --save-dev

  3. external-scripts.json 裡頭加入 hubot-deploy

  4. apps.json 中設定想要部署的 repos 有哪些:

    {
      "repo_name": {
        "provider": "capistrano",
        "auto_merge": false,
        "repository": "kjj6198/deploy101",
        "environments": ["production", "staging"]
      }
    }
    

    這些資料在 hubot 送出 deployment 時會一併塞入 payload 當中。像是這樣:

        payload: {
          "name": "repo_name",
          "robotName": "yourrobot",
          "hosts": "",
          "notify": {
            "adapter": "slack",
            "room": "123456789",
            "user": "123456789",
            "user_name": "kjj6198"
          },
          "config": {
            "provider": "capistrano",
            "auto_merge": false,
            "repository": "kjj6198/deploy101",
            "environments": [
              "production",
              "staging"
            ]
          }
        }

特別要注意的是,provider 的欄位之後會送給 heaven,所以 provider 的值必須是 heaven 有的(之後會提到),或是自己實作 Provider。

這樣子我們的 hubot 就算設定完成了。先部署到 heroku 上測試看看,部署到 heroku 很簡單:

heroku login
git init
git add .
git commit "init"
heroku create
git push heroku master

部署成功後,比較重要的變數有幾個:

變數名稱用途
HUBOT_GITHUB_TOKENGITHUB_TOKEN,到個人帳號 > settings > personal access tokens 設定。設定好權限,因為 hubot 只是用來建立 repo 的 deployment,勾選 repo 即可。
HUBOT_SLACK_TOKEN你的 slack-bot token。可以到這裡設定

全域變數可以到 heroku 的 dashboard 或是直接用 command line 設定:

heroku config:set HUBOT_GITHUB_TOKEN=abcccc
heroku config:set HUBOT_SLACK_TOKEN=abcccc

測試一下是否成功。在你設定的頻道中輸入 hubot deploy:version

{% asset_img "success.png" "success" %}

其中的 hubot 要跟你的機器人名稱相同,例如機器人的名稱為 tripmomo,那麼我就要輸入 tripmomo deploy:version

成功的話 hubot 會回應你目前的版本訊息。

  1. 確認 hubot 有送出 deployment 事件。輸入 hubot deploy app to statging

  2. 輸入 curl -H "Authorization: token YOUR_GITHUB_TOKEN" https://api.github.com/repos/my-github/my-repo/deployments 看看 deployment 是否建立成功。如果成功會回傳:

    {
        "url": "https://api.github.com/repos/my-github/my-repo/deployments/28301325",
        "id": 123456,
        "sha": "2e3xxxxxxxaaaaaaabbbbbbb",
        "ref": "develop",
        "task": "deploy",
        "payload": { // from apps.json
          "name": "my-app",
          "robotName": "tripmomo",
          "hosts": "",
          "notify": {
            "adapter": "slack",
            "room": "aabbccdd",
            "user": "aabbccdd",
            "user_name": "kalan.chen"
          },
          "config": {
            "provider": "capistrano",
            "auto_merge": false,
            "repository": "my-github/my-repo",
            "environments": [
              "production",
              "staging"
            ]
          }
        },
        "environment": "staging",
        "description": "deploy on staging from hubot-deploy-v0.13.27",
        "creator": {
          "login": "kjj6198",
          "id": 123456,
          "avatar_url": "https://avatars2.githubusercontent.com/u/123456?v=3",
          "gravatar_id": "",
          "url": "https://api.github.com/users/kjj6198",
          "html_url": "https://github.com/kjj6198",
          "followers_url": "https://api.github.com/users/kjj6198/followers",
          "following_url": "https://api.github.com/users/kjj6198/following{/other_user}",
          "gists_url": "https://api.github.com/users/kjj6198/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/kjj6198/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/kjj6198/subscriptions",
          "organizations_url": "https://api.github.com/users/kjj6198/orgs",
          "repos_url": "https://api.github.com/users/kalanchen/repos",
          "events_url": "https://api.github.com/users/kjj6198/events{/privacy}",
          "received_events_url":"https://api.github.com/users/kjj6198/received_events",
          "type": "User",
          "site_admin": false
        },
        "created_at": "2017-03-01T12:24:20Z",
        "updated_at": "2017-03-01T12:24:20Z",
        "statuses_url": "https://api.github.com/repos/my-github/my-repo/deployments/12345667/statuses",
        "repository_url": "https://api.github.com/repos/my-github/my-repo"
      }

    更多 deployment API 可以到 github deployment API 看看。

設定 heaven

  • heaven 將 repo clone 下來。
  • 設定全域變數
變數名稱用途
DEPLOYMENT_PRIVATE_KEY因為 heaven 是用 ssh 登入,需要 private key。如果 server 在 ec2 上,也可以用 pem 的方式來設定。
GITHUB_CLIENT_ID到個人設定頁面 > OAuth application 產生
GITHUB_CLIENT_SECRET到個人設定頁面 > OAuth application 產生
DATABASE_URLheaven 會建立資料庫紀錄 deployment
GITHUB_TOKENheaven 會使用 gist 來當作 stdout stderr。所以在設定 token 時記得把 gist 打勾勾。

其他的變數可以到 這裡 查看。

補充說明 DEPLOYMENT_PRIVATE_KEY:原始檔案長這樣

--
MJVGa/WNT9aFs63ykxLCdGzav8CfQ5vKXrLrllHXUYFaB2yaN72L+fSsXAy9zMs2
vy6wV2fB6j3YrVNCnBwUUNGTX9Ka6eeK98dCvHVyyE9Iz3CJAWZxaI03Px/xX9ps
M4kDWe7IA6+mnuCVSzwQVWMdOoAXbQbhGdfeixbqljNhJrKW/jA9w4BNarwGYv4E
0MwdU9x7zpk826ytza87yXHSdNuTKcsGQk4XHMYxJECj4EM8vTlVlEyEXZtCeh2z
P4bjYkTcBom4nC/q7Ea7Pmy1iDJqs0qc1L/xtNMypMhx4iIaeDVawkvBaL6t9IPT
KVuC9Y1uw5nJP1gwxXa5qoazhcikzqRYmaeWIzsZrcVShZBrJO9/a/APxXY7qJpJ
0r1YYTykw7THYj2QYiv8cfF64/vh9cB0NELEp5hIuS82Mf6CjqRR7QYR+By3uIdD
hQ77NMpQlmIC+TCJsLoADqwmEEZCiQSejtkXXtN/mNl581jP8+ViNkWZfPYWe7g6
yUeXVN1cBPo6AIu+lStE+SlR8lbu7sdpn6lid1pJf50zeythabze81y/nrAdx+Jn
scACBJBrERkhm2wdULkqwMV2g0U53YpYVAs2fFU1hGzRcE5zF1sdy9RLLX45Mzrm
lRErTbSUcnoQJhhCso5uNY6MMnr/rQF920KA0Ufr40IBcQ8bOSX7lJucST5bZLDg
H7g16rimHgK4I9rrvKy4plvbolfpuKGMYJDS3Q7IW5cL5lWLU3HaVSn+VyZe8p3A
prVx0XmSCwpmUzbDI6FoqniVPVdgis2tV1uKdnJPVn0DoK0ersosGXmALytbYLeE
arH/cIlGGCoGbIX+Iv3u8aICBEG2eR8eXmQSlGI5rp9hGK/JrlkL3PywVmPw4Efi
atiS6Y12Tuu8bdpPxBTzXK3PoZ23Pc+1l7NXXIzBeGnj56bALOIbAY5kg+lIRdtP
NSTAW8IVgFJUl4uzy/NXn/ewiE093ZVs59I2x4OoS14S20mkM/ldWbvlVm4Z3JxC
xIWsIV8aLznttic5MJUGjGoqH1Brg0o1HyWdkoEcC1N0G57oO4pN4UTD5co5xY9j
Ai2NIcFCYzqrdTfSlPWJBZLhjZ5hOXIwuTeJfRxDAVphaUqfpXb3o3URGRWiGENA
kIYKiq4XeNguwrFBzg5CB7NEKvjbjJ31GI26yAPa7yrKpuNFAjPpO6JKdL8slvx8
GXCOSbhGPFxzmtYzEeMxmnHqOa0Z953XeheKfJoipqRAyENxPBvclDonqVfxuTvw
cZzqFD+XjDJCJ5INwuwk2WupVzQjzV6TagcIX63Kq1Z9HSoFIBiCrdLzTMDG4Ro3
2wpN1tFQFz6alvwKtifGwhvG3qqmsfcQqw56gGY0DWIqG5x/thdG7UzZT7iMVDJV
LAO5wNnBK6L+feov9LqP7ONAonBVawmTv0ArjVhhkYZEi6d+ymvPpL1ORFAymLne
dpk4VmmmQvkUu0KudRqulavTIrnXFkuv2va+5X9mHGoNNMo1TXk2XX1eM4Rc7nAY
6IwPyAuFEtT5ocWBklB/qUZtdu4fG876o0X87GklR9ZfPG+tWpH2F+1j1mMHKuiP
--

要修改成:

--\nMJVGa/WNT9aFs63ykxLCdGzav8CfQ5vKXrLrllHXUYFaB2yaN72L+fSsXAy9zMs2\nvy6wV2fB6j3YrVNCnBwUUNGTX9Ka6eeK98dCvHVyyE9Iz3CJAWZxaI03Px/xX9ps\nM4kDWe7IA6+mnuCVSzwQVWMdOoAXbQbhGdfeixbqljNhJrKW/jA9w4BNarwGYv4E\n0MwdU9x7zpk826ytza87yXHSdNuTKcsGQk4XHMYxJECj4EM8vTlVlEyEXZtCeh2z\nP4bjYkTcBom4nC/q7Ea7Pmy1iDJqs0qc1L/xtNMypMhx4iIaeDVawkvBaL6t9IPT\nKVuC9Y1uw5nJP1gwxXa5qoazhcikzqRYmaeWIzsZrcVShZBrJO9/a/APxXY7qJpJ\n0r1YYTykw7THYj2QYiv8cfF64/vh9cB0NELEp5hIuS82Mf6CjqRR7QYR+By3uIdD\nhQ77NMpQlmIC+TCJsLohtJEmEEZCiQSejtkXXtN/mNl581jP8+ViNkWZfPYWe7g6\nyUeXVN1cBPo6AIu+lStE+SlR8lbu7sdpn6lid1pJf50zeythabze81y/nrAdx+Jn\nscACBJBrERkhm2wdULkqwMV2g0U53YpYVAs2fFU1hGzRcE5zF1sdy9RLLX45Mzrm\nlRErTbSUcnoQJhhCso5uNY6MMnr/rQF920KA0Ufr40IBcQ8bOSX7lJucST5bZLDg\nH7g16rimHgK4I9rrvKy4plvbolfpuKGMYJDS3Q7IW5cL5lWLU3HaVSn+VyZe8p3A\nprVx0XmSCwpmUzbDI6FoqniVPVdgis2tV1uKdnJPVn0DoK0ersosGXmALytbYLeE\narH/cIlGGCoGbIX+Iv3u8aICBEG2eR8eXmQSlGI5rp9hGK/JrlkL3PywVmPw4Efi\natiS6Y12Tuu8bdpPxBTzXK3PoZ23Pc+1l7NXXIzBeGnj56bALOIbAY5kg+lIRdtP\nNSTAW8IVgFJUl4uzy/NXn/ewiE093ZVs59I2x4OoS14S20mkM/ldWbvlVm4Z3JxC\nxIWsIV8aLznttic5MJUGjGoqH1Brg0o1HyWdkoEcC1N0G57oO4pN4UTD5co5xY9j\nAi2NIcFCYzqrdTfSlPWJBZLhjZ5hOXIwuTeJfRxDAVphaUqfpXb3o3URGRWiGENA\nkIYKiq4XeNguwrFBzg5CB7NEKvjbjJ31GI26yAPa7yrKpuNFAjPpO6JKdL8slvx8\nGXCOSbhGPFxzmtYzEeMxmnHqOa0Z953XeheKfJoipqRAyENxPBvclDonqVfxuTvw\ncZzqFD+XjDJCJ5INwuwk2WupVzQjzV6TagcIX63Kq1Z9HSoFIBiCrdLzTMDG4Ro3\n2wpN1tFQFz6alvwKtifGwhvG3qqmsfcQqw56gGY0DWIqG5x/thdG7UzZT7iMVDJV\nLAO5wNnBK6L+feov9LqP7ONAonBVawmTv0ArjVhhkYZEi6d+ymvPpL1ORFAymLne\ndpk4VmmmQvkUu0KudRqulavTIrnXFkuv2va+5X9mHGoNNMo1TXk2XX1eM4Rc7nAY\n6IwPyAuFEtT5ocWBklB/qUZtdu4fG876o0X87GklR9ZfPG+tWpH2F+1j1mMHKuiP\n-----END RSA PRIVATE KEY-----

既然公開,這組 private key 當然報廢了

設定 Gemfile

因為 heaven 的動作會是拉下最新的 repo 後,執行 cap ... deploy 的指令,所以 capistrano 的版本必須跟要部署的那個版本相同。同時,也要注意任何 asset 相關的 gem 也要一併放入 heaven。舉例來說,如果我的 Capfile 有用到

gem 'capistrano', '3.4.0'
gem 'capistrano3-unicorn'
gem 'capistrano-rails'
gem 'sitemap_generator'
gem 'capistrano-rvm'

那麼就要將這些 gem 加入 heaven 的 Gemfile 當中。因為 heaven 會將要部署的 repo 抓下來之後,進去資料夾輸入 cap staging ... deploy 的指令,所以如果沒有安裝相對應的 gem,heaven 就沒辦法部署了。

串接 github deployment

  • 先到 repo 的 settings > deploy key 加入 ssh-key。
  • 到 repo 的 settings > webhooks > add webhook
  • Payload URL 填入你的 heaven 部署 host 的網址,例如:https://yourapp.com.tw/events。如果想要修改,可以到 heaven repo 的 routes.rb 中修改
  • Content Type 選擇 application/json
  • Secret 依需求選填
  • 下面問你這個 webhook 要監聽哪些事件,我們是用 deployment 來做部署的,所以選擇 deployment 以及 deployment status。

部署

如果是部署到 heroku 的話,因為 heaven 要開 redis 跟 resque。記得加入相對應的 add-on 以及 REDIS_URL

同時別忘記了要建立資料庫 heroku run rake db:migrate

hubot-deploy 常用指令

  • hubot deploy:version 目前版本
  • hubot deploy repo: 根據 apps.json deploy 指定的 repo name。
  • hubot deploy repo/branch:將指定 repo 的某一個 branch 部署到預設的 environment 中。可設定 HUBOT_DEPLOY_DEFAULT_ENVIRONMENT 來決定
  • hubot deploy repo/branch to staging:將指定 repo 中的 branch 部署到 staging

筆記

  • heaven 的文件雖然不明所以,但是程式碼跟測試寫得蠻完整的,熟悉 ruby 的開發者甚至可以將整個 heaven 架設好,修改一下程式碼,加上 routes,直接建立 UI 一鍵部署。

  • OptionParser::AmbiguousOption: ambiguous option: -s:不確定是不是 Capistrano 更新之後指令有變動。解決方法是到 lib/heaven/provider/capistrano.rb 修改 deploy_command

    module Heaven
      # Top-level module for providers.
      module Provider
        # The capistrano provider.
        class Capistrano < DefaultProvider
     	.....
          def execute
            return execute_and_log(["/usr/bin/true"]) if Rails.env.test?
    
            unless File.exist?(checkout_directory)
              log "Cloning #{repository_url} into #{checkout_directory}"
              execute_and_log(["git", "clone", clone_url, checkout_directory])
            end
    
            Dir.chdir(checkout_directory) do
              log "Fetching the latest code"
              execute_and_log(%w{git fetch})
              execute_and_log(["git", "reset", "--hard", sha])
              deploy_command = [cap_path, environment, "部署的 cap 指令"]
              log "Executing capistrano: #{deploy_command.join(" ")}"
              execute_and_log(deploy_command)
            end
          end
        end
      end
    end
  • 因為 heaven 在部署時會使用 gist 當作 stdout 跟 stderr,在設定 GITHUB_TOKEN 的時候一定要記得把 gist 的 scope 打勾

  • Net::SSH::AuthenticationFailed: Authentication failed for user apps@staging.tripmoment.com :SSH private_key 設定有誤。先確定這組 ssh key 是否已經加入 github,再來確定將 passphrase 拿掉,並且將 ssh private key 變成一行加上 \n。

  • ArgumentError: Could not parse PKey: no start line 沒有將 SSH private key 的 passphrase 移除

後記

通常在公司裡頭,開發團隊人數不多的話,devops 都是由後端兼任的,前端比較少接觸。不過用「我是前端,我不需要管 devops」這種藉口搪塞自己不去學習好像也說不太過去,畢竟開發一個健全的系統絕對不可能只有前端而已。

這篇文章試著將文件中沒有提到或是省略的步驟整合起來,heaven 跟 hubot-deploy 的文件中有太多沒有提到的細節,導致整合起來時需要花不少時間試錯。希望能夠節省大家踩雷跟翻原始碼的時間。

這篇文章還有許多 devops 的細節沒有詳述,畢竟建立一套完整的 devops pipeline 需要時間,自己對於 CI/CD 的設定也還不夠熟悉。

參考資源:

Prev

IT iron man to help finish the game experience

Next

Made a weekly magazine - Japanese 800

If you found this article helpful, please consider buy me a drink ☕️ It'll make my ordinary day shine✨

Buy me a coffee