利用 Ansible 部署 GitHub 專案的設定細節 by William Yeh | CodeData
top

利用 Ansible 部署 GitHub 專案的設定細節

分享:

這是個 DevOps 種族大融合的時代。各工具陸陸續續(搶地盤撈過界?)跨出傳統勢力範圍,吸納其他工具的優點。如果是很自然的延伸,且延伸得夠完整,對使用者來說,就等於是賺到一把好用的瑞士刀。

像甫列名 2014 年十大開源專案Ansible,除了在傳統的組態管理 (configuration management) 地盤與 ChefPuppetSalt 競爭之外,也跨足傳統上由 FabricCapistrano 佔據的 application deployment 地盤。對我來說,這就是一把好用的瑞士刀。

本篇文章要談談利用 Ansible 來部署位於 GitHub 的專案時,要注意的小地方。

為了方便讀者印證,本文有兩處標上「換你動手試試看!」記號的部分,都有完整的範例可讓你親手實驗。請先安裝 Ansible 及 Vagrant 軟體,並下載本文的範例程式:

$ git clone http://github.com/William-Yeh/ansible-git-deploy-demo.git

部署 GitHub 的公開專案

先從簡單的例子談起,也藉此補充一些 Ansible 相關背景知識,尤其是 Ansible Galaxy 服務。

用 bash 指令安裝

如果我們想 git clone 下來的東西,是個不需身分認證的公開專案,一切就很簡單。就以 wrk 這套用 C 寫的 HTTP 壓力測試軟體為例,我們會如此把它下載部署在 Linux server 上:

$ # 先登入至 Linux server 主機
$ ssh MY-SERVER

$ # 下載專案原始檔
$ git clone http://github.com/wg/wrk.git

$ # 切換目錄
$ cd wrk
$ # 編譯
$ make
$ # 安裝
$ sudo cp wrk /usr/local/bin

在 Ansible 裡引用外部 role(s)

如何改用 Ansible 來重現上述的安裝程序呢?

既然要動用 Ansible 這把瑞士刀,我們就會盡可能設計得周全穩固;尤其是,不要做過多假設。

什麼叫做「不要做過多假設」呢?

就拿前面列的一連串指令而言,其實已經偷偷隱藏一項假設:「該主機已經安裝過 git 及 make 軟體。」這項假設先成立了,才能直接用 git clone 指令抓專案檔案、用 make 指令來編譯。可是這項假設並不總是正確的!為了瘦身或資安考量,雲端服務商提供的 server 映像檔裡,可能不會幫你預載常用的工具軟體。像 Amazon EC2 和 Google Compute Engine 提供的 Ubuntu 14.04 映像檔,就沒有 git 及 make,你會看到類似這樣的錯誤訊息:

$ git
The program 'git' is currently not installed. To run 'git' please ask your administrator to install the package 'git'

$ make
The program 'make' is currently not installed. You can install it by typing:
sudo apt-get install make

因此,第一步,我們必須先確保目標機器上面有安裝要用到的工具軟體。

Git 和 make 都是常會用到的軟體,一定不只我一個人有這種需求。所以,我會先去 Ansible Galaxy 這個 Ansible role 資源大本營,找找別人分享出來的 role 設定。評估後,geerlingguy.gitkun432.build-essential 這兩個 roles 很合我用,又支援多種主流的 Linux distributions,我就不必重新製造輪子了。

如果想從 Ansible Galaxy 引用的 role 只有一兩個,可能會直接手動安裝。譬如說以下指令,會將 geerlingguy.gitkun432.build-essential 這兩個 roles 下載至此工作目錄的 roles 目錄中:

$ ansible-galaxy install -f  -p roles  \
      geerlingguy.git  kun432.build-essential

不過,既然都已經動用到 Ansible 這種 “infrastructure as code” 等級的工具,就應該把所有組態,連同引用的外部 roles 名稱,都寫進描述檔,才算一以貫之。所以,更正統的方法是,把想從 Ansible Galaxy 引用過來的 role(s),先列在一個名叫 requirements.yml 的檔案:

---
- src: geerlingguy.git
  path: roles/

- src: kun432.build-essential
  path: roles/

再叫 ansible-galaxy 整批下載過來:

$ ansible-galaxy install -f  -r requirements.yml

如此一來,geerlingguy.gitkun432.build-essential 這兩個 roles 就會乖乖躺在此工作目錄的 roles 子目錄裡面,等著被我們引用:

$ tree
.
├── requirements.yml
└── roles
    ├── geerlingguy.git
    │   ├── README.md
    │   ├── defaults
    │   │   └── main.yml
    │   ├── meta
    │   │   └── main.yml
    │   ├── tasks
    │   │   ├── install-from-source.yml
    │   │   └── main.yml
    │   ├── tests
    │   │   ├── inventory
    │   │   ├── test_install_from_source.yml
    │   │   └── test_install_package.yml
    │   └── vars
    │       └── main.yml
    └── kun432.build-essential
        ├── LICENSE
        ├── README.md
        ├── meta
        │   └── main.yml
        ├── tasks
        │   └── main.yml
        ├── test.yml
        └── vars
            ├── CentOS.yml
            ├── Debian.yml
            ├── Fedora.yml
            ├── FreeBSD.yml
            ├── RedHat.yml
            ├── Suse.yml
            └── Ubuntu.yml

之後,我們的 playbook 就可以這樣使用它們:

- hosts: all
  roles:
    - geerlingguy.git
    - kun432.build-essential

在 Ansible 裡使用 git

一旦確定目標機器上面已備妥 git 軟體,就可透過 Ansible 的 git 指令去把 GitHub 的公開專案 clone 回來。整份 playbook 可以寫成這樣:

- hosts: all
  sudo: True
  roles:
    - geerlingguy.git
    - kun432.build-essential

  tasks:
    - name: clone project from git repo
      git: repo={{ project_git_repo }} dest={{ project_source_path }}

    - name: install prerequisite libs
      apt: name=libssl-dev state=present update_cache=yes

    - name: compile
      command: make
      args:
        chdir: "{{ project_source_path }}"
        creates: "{{ project_source_path }}/wrk"

    - name: install
      command: cp wrk /usr/local/bin
      args:
        chdir: "{{ project_source_path }}"
        creates: /usr/local/bin/wrk

  vars:
    - project_git_repo: http://github.com/wg/wrk.git
    - project_source_path: /tmp/wrk

換你動手試試看!

嗡嗡嗡,上工了!

想動手實驗上述「用 Ansible 部署 GitHub 的公開專案」的例子,請先切換至範例程式的 public-repo 子目錄:

$ cd public-repo

接著,下載我們要引用的外部 Ansible roles:

$ ansible-galaxy install -f  -r requirements.yml

前置作業就緒,可以開始透過 Ansible 安裝部署了。

先在本機的 Ubuntu 虛擬機牛刀小試一下:

$ # 啟動虛擬機,並順便透過 Ansible 設定部署此虛擬機組態
$ vagrant up

$ # OK,可以登入至虛擬機檢查看看
$ vagrant ssh

有把握了,再試試雲端主機吧!

如果標的是 Amazon EC2,請先編輯主機列表檔(Ansible 的術語叫做 “inventory”):

$ vi hosts-ec2

再叫 ansible-playbook 設定部署之:

$ ansible-playbook  \
      --private-key $HOME/.ssh/MY-EC2-PRIVATE-KEY.pem  \
      -i hosts-ec2  \
      playbook.yml

如果標的是 Google Compute Engine,同樣的,請先編輯主機列表檔:

$ vi hosts-gce

再叫 ansible-playbook 設定部署之:

$ ansible-playbook  \
      --private-key $HOME/.ssh/MY-GCE-PRIVATE-KEY  \
      -i hosts-gce  \
      playbook.yml

OK,「用 Ansible 部署 GitHub 的公開專案」已經沒問題了,接下來,就要探討比較複雜的「用 Ansible 部署 GitHub 的私有專案」。

部署 GitHub 的私有專案

用 bash 指令安裝

如果我們想 git clone 下來的東西,是個需要身分認證的私有專案,就複雜一點了。

就以 ACCOUNT/PROJECT 這個虛構的 GitHub 私有專案為例,如果我們想如此把它下載部署在 Linux server 上,會有小問題:

$ # 先登入至 Linux server 主機
$ ssh MY-SERVER

$ # 試圖下載專案原始檔...
$ git clone [email protected]:ACCOUNT/PROJECT.git
Cloning into 'PROJECT'...
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

$ # 檢驗一下對 github.com 的 SSH 連線...
$ ssh -T [email protected]
Permission denied (publickey).

這台 server 無法存取 GitHub 私有專案的原因是,我們賴以存取 GitHub 私有專案的 private key,只擺在本機端,並未複製一份在 server 上。事實上,除非必要,也不鼓勵把 private key 存放在 server 上,徒增資安風險。

正統方法是透過 SSH agent forwarding 機制,將本機端的 private key 轉送至遠端 server(以授予它存取 GitHub 私有專案的權限),而且效力僅限這一次的 SSH session(比複製一份 private key 在 server 上還要安全)。讓我們以 agent forwarding 模式再試一次:

$ # 先以 agent forwarding 模式(-A 參數),登入至 Linux server 主機
$ ssh -A MY-SERVER

$ # 檢驗一下對 github.com 的 SSH 連線,agent forwarding 是否生效...
$ ssh -T [email protected]
Hi William-Yeh! You've successfully authenticated, but GitHub does not provide shell access.

$ # 下載專案原始檔
$ git clone [email protected]:ACCOUNT/PROJECT.git
Cloning into 'PROJECT'...
The authenticity of host 'github.com (192.30.252.129)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,192.30.252.129' (RSA) to the list of known hosts.
remote: Counting objects: 671, done.
remote: Total 671 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (671/671), 132.66 KiB | 0 bytes/s, done.
Resolving deltas: 100% (435/435), done.
Checking connectivity... done.

關於 SSH agent forwarding 的運作方式還不明瞭的,ihower(張文鈿)在〈SSH agent forwarding 的應用〉一文有很簡要的介紹,請勿錯過。

在 Ansible 裡運用 agent forwarding 機制

如何改用 Ansible 來重現上述的安裝程序呢?有三個環節要處理。

首先,要寫一個 ansible.cfg 設定檔,叫 Ansible 以 agent forwarding 模式與主機溝通:

[ssh_connection]
ssh_args = -o ForwardAgent=yes

如果 server 主機不是雲端主機,而是用 Vagrant 建立的虛擬機,據 Vagrant 官方文件所述,還需要在 Vagrantfile 也開啟 agent forwarding 模式:

Vagrant.configure(2) do |config|
  config.ssh.forward_agent = true
end

其次,以 agent forwarding 模式登入 server 之後,如果以 sudo 模式執行任何命令,某些環境變數會被吃掉,與 agent forwarding 息息相關的 SSH_AUTH_SOCK 環境變數也包括在內。解決之道呢?綜合 Stack OverflowGitHub Help 各家說法,影響層面最小的,就是在 git 指令前面強制設定 sudo: False

- hosts: all
  tasks:
    - name: clone project from git repo
      sudo: False
      git: ...

或者,直接導入我寫的 williamyeh.sudo-agent-forwarding role 來處理 SSH_AUTH_SOCK 環境變數。

最後,ansible-playbook 無法像前面舉的 bash 例子一樣,隨時透過 stdin 裝置與使用者互動。也就是說,它無法秀出這樣的訊息,讓我們有機會手動輸入 yes 來放行:

The authenticity of host 'github.com (192.30.252.129)' can't be established.  
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.  
Are you sure you want to continue connecting (yes/no)? yes  
Warning: Permanently added 'github.com,192.30.252.129' (RSA) to the list of known hosts.

因此,在 Ansible 情境中,必須先將「github.com 的 host public key」添加到我們 server 主機的 $HOME/.ssh/known_hosts/etc/ssh/ssh_known_hosts 列表中,讓 server 能對 github.com 網站驗明正身,後續的 git clone 步驟才能通行不被阻擋。

Ansible Galaxy 裡面有一個 bfmartin.ssh_known_hosts role 剛好符合這種需求。有了它,就不必手動處理 known_hosts 及 ssh-keyscan 等繁瑣細節,直接引用即可:

- hosts: all
  roles:
    - { role: bfmartin.ssh_known_hosts, host: github.com }

綜上所述,逐一將 agent forwarding、sudo 及 known_hosts 三個環節都打通了,Ansible 就能正常處理 GitHub 的私有專案,又不增加資安風險。整份 playbook 可以寫成這樣:

- hosts: all
  sudo: True
  roles:
    - geerlingguy.git
    - { role: bfmartin.ssh_known_hosts, host: github.com }
    - williamyeh.sudo-agent-forwarding

  tasks:
    - name: clone project from git repo
      sudo: False
      git: repo={{ project_git_repo }} dest={{ project_source_path }}

  vars:
    - project_git_repo: [email protected]:ACCOUNT/PROJECT.git
    - project_source_path: /tmp/project

換你動手試試看!

嗡嗡嗡,上工了!

想動手實驗上述「用 Ansible 部署 GitHub 的私有專案」的例子,請先切換至範例程式的 private-repo 子目錄:

$ cd private-repo

接著,下載我們要引用的外部 Ansible roles:

$ ansible-galaxy install -f  -r requirements.yml

也別忘了修改 playbook.yml 裡面的 project_git_repo 變數,填上你有權存取的私有專案名稱。

前置作業就緒,可以開始透過 Ansible 安裝部署了。

先在本機的 Ubuntu 虛擬機牛刀小試一下:

$ # 啟動虛擬機,並順便透過 Ansible 設定部署此虛擬機組態
$ vagrant up

$ # OK,可以登入至虛擬機檢查看看
$ vagrant ssh

有把握了,再試試雲端主機吧!

如果標的是 Amazon EC2,請先編輯主機列表檔(Ansible 的術語叫做 “inventory”):

$ vi hosts-ec2

再叫 ansible-playbook 設定部署之:

$ ansible-playbook  \
      --private-key $HOME/.ssh/MY-EC2-PRIVATE-KEY.pem  \
      -i hosts-ec2  \
      playbook.yml

如果標的是 Google Compute Engine,同樣的,請先編輯主機列表檔:

$ vi hosts-gce

再叫 ansible-playbook 設定部署之:

$ ansible-playbook  \
      --private-key $HOME/.ssh/MY-GCE-PRIVATE-KEY  \
      -i hosts-gce  \
      playbook.yml

一切 OK!

後話

這篇文章,其實是個副產品。

一開始,我想在 Ansible 世界尋找 Capistrano 替代品。我在 Ansible Galaxy 網站用 “deploy” 和 “capistrano” 之類的字眼搜尋,找到一個不錯的 f500.project_deploy role。不過,在用它部署 GitHub 私有專案時,觸礁了。也因此開始了我的除雷之旅,才有這篇文章的經驗總結。

過程中,我也對前述的 f500.project_deploy 做了一些修改。如果你想引用我的修改版,請先把它寫進 requirements.yml

---
- src: http://github.com/William-Yeh/ansible-project_deploy
  name: project_deploy
  path: roles/

再用 ansible-galaxy 指令下載 role 內容。下載完畢後,就可以在你的 playbook 直接引用它:

- hosts: all
  roles:
    - project_deploy

  vars:
    - project_git_repo: ...
    - project_source_path: ...

Happy coding!

分享:
按讚!加入 CodeData Facebook 粉絲群

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

擔任過許多職場角色:程式設計師、技術團隊領班、技術作家及譯者、教授、顧問、技術佈道者,但目前最喜歡的身份,還是「軟體架構師」。

熱門論壇文章

熱門技術文章