前言
目前所在的公司裡頭是直接在本地端的 terminal 跑 cap staging deploy
指令。 capistrano 作為自動部署化的工具非常方便,但難免會遇到幾個問題:
- 不是團隊中的每個人都有相同的環境
- 大家都在部署,結果 staging 上現在到底是哪個 branch,完全一頭霧水。
- deploy 這件事情卡在本地端。
對一家新創來說,越穩定的開發效率和流程,就越能夠專注在產品當中。所以我們希望做到幾件事:
- 開發團隊都可以輕鬆的部署
- 不用在本地端下指令部署,還要多設定 ssh。
- 就算沒有開著電腦,也可以輕鬆地部署
- 能夠記錄部署的狀況
- 如果出問題了,可以快速 rollback 回上一個版本
逐漸厭倦了在 terminal 打指令,ssh key 手動加的日子。於是打算自己研究有沒有更流暢的部署流程。
之前在 Sudo 裡頭,幸好有 @ocowchun 跟 @henry 兩位懶工,devops 做得非常完整,才能夠專注在開發功能,而不是一堆繁複的設定當中。(雖然才剛開發完就關閉服務了…)
目前覺得最合適的解決方案是搭配 hubot-deploy
以及 heaven
來幫助部署。
但 heaven 的文件實在寫的有夠爛。
看了老半天,甚至看了一下 source code 才知道到底該怎麼設定。於是決定將整個設定流程分享給大家,希望能夠減少其他 devops 們走歪路的時間。
主要流程
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
利用 yeoman 產生 hubot,並且選擇
adapter
為slack
。在
package.json
中加入hubot-deploy
,或者 runnpm install hubot-deploy --save-dev
在
external-scripts.json
裡頭加入hubot-deploy
。到
apps.json
中設定想要部署的 repos 有哪些:12345678{"repo_name": {"provider": "capistrano","auto_merge": false,"repository": "kjj6198/deploy101","environments": ["production", "staging"]}}這些資料在 hubot 送出 deployment 時會一併塞入 payload 當中。像是這樣:
1234567891011121314151617181920payload: {"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 很簡單:
|
|
部署成功後,比較重要的變數有幾個:
變數名稱 | 用途 |
---|---|
HUBOT_GITHUB_TOKEN | GITHUB_TOKEN,到個人帳號 > settings > personal access tokens 設定。設定好權限,因為 hubot 只是用來建立 repo 的 deployment,勾選 repo 即可。 |
HUBOT_SLACK_TOKEN | 你的 slack-bot token。可以到這裡設定 |
全域變數可以到 heroku 的 dashboard 或是直接用 command line 設定:
|
|
測試一下是否成功。在你設定的頻道中輸入 hubot deploy:version
其中的 hubot
要跟你的機器人名稱相同,例如機器人的名稱為 tripmomo,那麼我就要輸入 tripmomo deploy:version
。
成功的話 hubot 會回應你目前的版本訊息。
確認 hubot 有送出 deployment 事件。輸入
hubot deploy app to statging
輸入
curl -H "Authorization: token YOUR_GITHUB_TOKEN" https://api.github.com/repos/my-github/my-repo/deployments
看看 deployment 是否建立成功。如果成功會回傳:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152{"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_URL | heaven 會建立資料庫紀錄 deployment |
GITHUB_TOKEN | heaven 會使用 gist 來當作 stdout stderr。所以在設定 token 時記得把 gist 打勾勾。 |
其他的變數可以到 這裡 查看。
補充說明 DEPLOYMENT_PRIVATE_KEY
:原始檔案長這樣
|
|
要修改成:
|
|
既然公開,這組 private key 當然報廢了
設定 Gemfile
因為 heaven 的動作會是拉下最新的 repo 後,執行 cap ... deploy
的指令,所以capistrano 的版本必須跟要部署的那個版本相同。同時,也要注意任何 asset 相關的 gem 也要一併放入 heaven。舉例來說,如果我的 Capfile 有用到
|
|
那麼就要將這些 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
1234567891011121314151617181920212223242526module Heaven# Top-level module for providers.module Provider# The capistrano provider.class Capistrano < DefaultProvider.....def executereturn 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])endDir.chdir(checkout_directory) dolog "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)endendendendend因為 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 的設定也還不夠熟悉。