2015年11月11日 星期三

Ansible 筆記 - 透過 ansible 指令、ansible-playbook 執行批次腳本等用法快速上手 @ Ubuntu 14.04

剛接手一份用 Puppet 維護的服務,結果 Puppet 還沒玩熟,就先來改玩一下 Ansible,而 Ansible 也是一套機器部署的軟體,最大特色是不需要 master-slave(client) 架構,也就是被管理的機器不用安裝 ansible 啦!另外還有類似市集功能,例如你想要找 nginx 的設定方式,那只要上市集找一下,接著把它下載回來,套用一下,即可部署,十分便利。

首先先摸一下 ansible 指令,熟了再把玩 ansible-playbook 批次指令,之後再從 ansible-galaxy 從市集找設定檔。

安裝 ansible ( http://docs.ansible.com/ansible/intro_installation.html#id17 ):

$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
$ ansible --version
ansible 1.9.4


接著,來試試 ansible 指令吧!這個指令的用法就像執行 ssh remote commands 一樣:

$ ansible localhost -m raw -a "date"
localhost | success | rc=0 >>
Wed Nov 11 11:01:21 UTC 2015

$ ansible localhost -m setup
...用 JSON Format 列出系統資訊...

$ ansible localhost -m setup | grep -i ubuntu
        "ansible_distribution": "Ubuntu",
            "description": "Ubuntu 14.04.3 LTS",
            "id": "Ubuntu",


然而,localhost 是一個默認的字眼,若要對遠端機器操作時,必須先寫定 hosts 對應表,預設是使用 /etc/ansible/hosts 位置,但也可以透過 -i filename 來使用,這裡使用 -i 來試試,並用 127.0.0.1 為例。

$ cat hosts
[webserver]
127.0.0.1

$ ansible -i etc/ansible/hosts webservers --list-hosts
    127.0.0.1

$ ansible -i etc/ansible/hosts webservers -m raw -a "date"
127.0.0.1 | FAILED | rc=255 >>

$ ansible -i etc/ansible/hosts --private-key=~/.ssh/id_rsa webservers -m raw -a "date"
127.0.0.1 | success | rc=0 >>
Wed Nov 11 11:27:36 UTC 2015

$ ansible -i etc/ansible/hosts --private-key=~/.ssh/id_rsa webservers -m setup | grep -i cpu
            "Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz",
            "Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz"
        "ansible_processor_vcpus": 2,


其中 -m 代表要採用的模組,而 -a 是該模組的參數資訊。 raw 代表 -a 後頭的指令就給他執行下去就對了。所以一些常用的 ssh remote command 則是可以用 -m raw 來替代。其中 --private-key 是指定登入用的 key,而 -u 還可以指定登入的使用者。

接下來透過 yml 檔案描述來批次處理大量指令:

$ cat webserver.yml
---
- hosts: webserver
  tasks:
  - shell: date

$ ansible-playbook -i etc/ansible/hosts --private-key=~/.ssh/id_rsa webserver.yml
PLAY [webserver] **************************************************

GATHERING FACTS ***************************************************************
ok: [127.0.0.1]

TASK: [shell date] ************************************************************
changed: [127.0.0.1]

PLAY RECAP ********************************************************************
127.0.0.1            : ok=2    changed=1    unreachable=0    failed=0


以上是非常簡單的批次任務,在複雜下去之前,先介紹一招透過 yml 檔案來新增 ssh remote key 方式(等同於 ssh-copy-id 功能):

$ cat add_authorized_key.yml
- hosts: '{{host}}'
  remote_user: '{{user}}'
  vars:
      ssh_private_key_path: '{{ ssh_key_file }}'

  tasks:
  - name: Add RSA key to the remote host
    authorized_key: user='{{user}}' key="{{lookup('file', ssh_private_key_path )}}"

$ ansible-playbook add_authorized_key.yml -i hosts

PLAY [{{host}}] ***************************************************************
skipping: no hosts matched

PLAY RECAP ********************************************************************

$ ansible-playbook add_authorized_key.yml -i hosts --ask-pass --extra-vars "user=ubuntu host=localhost ssh_key_file=ansible-deploy.pub"                                                                                                            
SSH password:

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [Add RSA key to the remote host] ****************************************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0


這個過程就是透過 add_authorized_key.yml 腳本,以及搭配的參數去執行,將 ansible-deploy.pub 檔案添加至 ubuntu@localhost 的 ~/.ssh/authorized_key 且確保此動作重複執行也不會添加重複筆數,在執行一次得到的結果:

$ ansible-playbook add_authorized_key.yml -i hosts --extra-vars "user=ubuntu host=localhost ssh_key_file=ansible-deploy.pub"                                                                                                            
SSH password:

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [Add RSA key to the remote host] ****************************************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0


可以清楚看到最後一個回報的 changed=0 ,代表此執行執行成功但沒有改變東西。且由於 key 已經添加,這次可以去掉 --ask-pass 的用法。這件事代表可以每次都重跑一樣的工作,但已經生效的不會再重做。

接著來寫個範本是每次執行都會把 EC2 系統更新至最新版(-u 代表要用 ubuntu 帳號登入,-s 則是用 sudo 執行):

$ cat update-ec2-ubuntu.yml
---
- hosts: ec2-servers
  tasks:
  - apt: upgrade=dist

$ ansible-playbook update-ec2-ubuntu.yml -i hosts --private-key=ansible-deploy.pem -u ubuntu -s
PLAY [ec2-servers] **************************************************

GATHERING FACTS ***************************************************************
ok: [XX.XX.XX.XX]

TASK: [apt upgrade=dist] ******************************************************

changed: [XX.XX.XX.XX]

PLAY RECAP ********************************************************************
XX.XX.XX.XX             : ok=2    changed=1    unreachable=0    failed=0


同理再跑一次,可以看到 changed=0,代表第二次已沒有需要更新的。

未來則就可以依照需求撰寫各台機器的部署腳本,其中還有更豐富的 roles 跟 ansible-galaxy 還沒把玩,晚點再來試試吧。

2015年11月10日 星期二

[Linux] 架設 Jenkins 筆記,以 Git plugin 與 PHP CodeIgniter - command-line interface (CLI) 定期任務為例 @ Ubuntu 14.04

架設還滿簡單的,幾行指令搞定:

$ wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
$ sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
$ sudo apt-get update
$ sudo apt-get install jenkins
$ sudo service jenkins start


如此一來,預設在 http://localhost:8080 就可以瀏覽到。

接著安裝一些工具,之後會用到:

$ sudo apt-get install git

接著紀錄一下自己的操作設定:
  • 管理 Jenkins -> 設定全域安全性
    • 勾選 Disable remember me
    • 安全性領域
      • 使用 Jenkins 內建使用者資料庫
    • 授權
      • 矩陣型安全性
        • 關閉 匿名使用者 所有權限
  • 管理 Jenkins -> 管理外掛程式
    • 更新
      • 把所有項目更新
    • 可用的
      • 搜尋 GIT plugin 跟 GIT client plugin 並安裝(若系統沒有 git,安裝完可以在 Jenkins 組態裡看到一些錯誤訊息)
其中授權那塊,必須先建立使用者,可以先從"登入成功的使用者可以做任何事"開始設定,過程會導入到建立使用者的流程。若有設定錯誤的地方導致無法管理時,可以修改 /var/lib/jenkins/config.xml 將 <useSecurity>true</useSecurity> 改成 <useSecurity>false</useSecurity> 後,再重新啟動 jenkins (sudo service jenkins restart)即可解決。

如此一來,就建立好 Jenkins 環境,而新工作也能夠用 git 了!擬個範例試試:假設專案在 bitbucket 管理,並使用 PHP CodeIgniter 架構,定期透過此專案來執行分析任務

作法:
  • Jenkins -> 新增作業 -> 填寫作業名稱(StudyJenkinsRoutine) -> 建置 Free-Style 軟體專案
    • 原始碼管理
      • Git
        • Repository URL: git@bitbucket.org:group/project.git
        • Credentials -> Add -> SSH Username with private key -> Enter directly (搭配 Bitbucket SSH 存取即可)
        • Additional Behaviours -> Advanced sub-modules behaviors -> 勾選 Recursively update submodules
    • 建置觸發程序
      • 定期建置(每十五分鐘跑一次,語法跟 crontab 一樣,但會建議用 H/15 來使用)
        • */15 * * * *  
    • 建置
      • 執行 Shell
        • php $WORKSPACE/index.php controller/function
如此一來,透過 Jenkins 的 Web 介面就可以設定與執行一些功能了,唯一留意的是執行工作上是否擁有相關的環境資源,例如範例是執行 PHP Framework 做事,那就得確保 php5-cli 裝了沒,若做的事跟資料庫相關,又得確定 php5-mysql 裝了沒等等。此外,上頭執行的 Shell 有用到 Jenkins 環境變數,要留意有些資料是跟 Jenkins 建立的工作名稱是相關的,例如工作名稱若有空白、等號以及相關的特殊符號時,極有可能會影響 shell script 的執行的。

關於 Jenkins 相關的路徑安排都跟 jenkins 帳號相關,例如 Jenkins 家目錄在 /var/lib/jenkins ,而添加一個任務名為 StudyJenkinsRoutine 時,其 $WORKSPACE 會是 /var/lib/jenkins/jobs/StudyJenkinsRoutine/workspace 的,這個位置也是 git clone 的資料所在。

最後,如果 Jenkins 是架設在 AWS EC2 上,若可以搭配 ELB 跟 SSL 憑證時,可以偷懶使用 ELB 將 https 流量導向到 Jenkins 8080 ,可以無痛提供 https 服務囉。

2015年11月4日 星期三

[PHP] 把玩 PHP AWS SDK - 列出 EC2 指定規格列表、 ELB 底下的機器列表 @ Ubuntu 14.04

之前用一些 tool-based 的工具做了一些事情,例如呼叫完指令再動態抽 JSON 資料:
  • $ aws elb describe-load-balancers --load-balancer-name "XXX"
  • $ aws ec2 describe-instances --instance-ids  "XXX"
來試試 AWS SDK 來做事吧,先挑 PHP !

為了高移植性,我採用下載 aws.phar 的模式:
接著則是簡單的登入資料填寫一下:

<?php
require 'aws.phar';

$config = array(
'version' => 'latest',
'credentials' => array(
'key' => 'key',
'secret' => 'secret',
),
'region' => 'us-west-2',
);


列出 EC2 - m1.small instance:

<?php
$ec2Client = Aws\Ec2\Ec2Client::factory($config);

// http://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.Ec2.Ec2Client.html
$result = $ec2Client->DescribeInstances(array(
        'Filters' => array(
                 array('Name' => 'instance-type', 'Values' => array('m1.small')),
        )
));

print_r($result);


想要列出 EC2 - 指定 Load Balancer 下的機器:

<?php
$client = Aws\ElasticLoadBalancing\ElasticLoadBalancingClient::factory($config);
$result = $client->DescribeLoadBalancers( array(
        'LoadBalancerNames' => array('Load Balancer Name')
));
print_r($result['LoadBalancerDescriptions'][0]['Instances']);
print_r($result);


其中,雖然 $result 都是 Aws\Result Object ,且裡頭都是 [data:Aws\Result:private] => Array ,但可以透過 array access 的方式去存取囉。

接下來就可以試試連續動作,從 ELB 得知機器後,再得到機器的 public ip:

<?php

require 'aws.phar';

$ELB_NAME = 'XXXX';
$client = Aws\ElasticLoadBalancing\ElasticLoadBalancingClient::factory($config);
$result = $client->DescribeLoadBalancers( array('LoadBalancerNames' => array($ELB_NAME)));
if (isset($result['LoadBalancerDescriptions']) && is_array($result['LoadBalancerDescriptions']) && count($result['LoadBalancerDescriptions']) && isset($result['LoadBalancerDescriptions'][0]['Instances'])) {
$instances = $result['LoadBalancerDescriptions'][0]['Instances'];
//print_r($instances);
$ec2_id = array();
foreach($instances as $ec2)
if (isset($ec2['InstanceId']))
array_push($ec2_id, $ec2['InstanceId']);
if (count($ec2_id) > 0) {
$client = Aws\Ec2\Ec2Client::factory($setup);
$result = $client->DescribeInstances(array(
'Filters' => array(
array('Name' => 'instance-id', 'Values' => $ec2_id),
)
));
}
$public_ip = array();
if (isset($result['Reservations'])) {
foreach($result['Reservations'] as $target) {
//print_r($target['Instances'][0]['PublicIpAddress']);
array_push($public_ip, $target['Instances'][0]['PublicIpAddress']);
}
}
//print_r($result['Reservations']);
print_r($public_ip);
}

2015年11月3日 星期二

[Linux] 透過 rpmbuild 建立 RPM @ CentOS 6.6

RPM 的製作過程真叫人又愛又恨 XD 好多細節啊。例如執行 rpmbuild 的角色,通常會在家目錄出現 rpmbuild 目錄結構等,整體上建立 rpm 有四個步驟:
  1. 建立相關的目錄結構  rpmbuild/BUILD rpmbuild/BUILDROOT rpmbuild/RPMS rpmbuild/SOURCES rpmbuild/SPECS  rpmbuild/SRPMS
  2. 將資料擺進 rpmbuild/SOURCES 位置
  3. 撰寫 rpmbuild/SPECS/example.spec
  4. 切換進 rpmbuild/SPECS 執行 rpmbuild -bb example.spec
若上述一切正常,在 rpmbuild/RPMS/ 裡的目錄長出 rpm 檔案。

這邊要筆記的是 example.spec 這個檔案格式:
  • % 開頭的是 spec 的保留指令(Macro),例如 %define 是定義變數,而 %description, %prep, %setup, %build, %install, %files, %changelog, %clean 則是對應的狀態
    • %prep 下方的位置代表預備動作
    • %setup 代表進行 Source 解壓縮
    • %build 例如執行 make 指令
    • %install 開始執行建立 RPM 的動作,可把所需檔案進行搬移
    • %files 成列出真正要打包的檔案列表,若沒列的話,RPM 會產生不了
    • %clean 打包完 rpm 後的動作,例如清掉資料等
  • 其他資訊
除了這些以外,spec 還定義了 package info:
  • Summary: 
  • Name:
  • Version: 1.0
  • Release: 1
  • Vendor: vendor
  • Packager: packager
  • License: BSD
  • Group: Applications/File
  • SOURCE: your_source_be_extraced_at_setup_macro.tar.gz
  • BuildArch: noarch
  • BuildRoot: %BuildRoot
範例:

%define Name test-repo
%define Source test-repo.tar.gz
%define Version 1.0
%define Release 1
%define RPM_ARCH x64_86
%define OWNER root
%define Vendor no
%define Packager no
%define License MIT
%define Group Applications/File

%define INSTALL_DIR /opt/%NAME

%define _topdir /tmp/rpm-build
%define RPM_BUILD_ROOT _topdir
%define BuildRoot /tmp/rpm-build/BUILDROOT/%Name-%Version-%Release.%RPM_ARCH

Summary: %Name
Name: %Name
Version: %Version
Release: %Release
Vendor: %Vendor
Packager: %Packager
License: %License
Group: %Group
SOURCE: %Source

%description

%prep

%setup -q

%build

%install
install -d %BuildRoot/%INSTALL_DIR
cp -r $PACKAGE_DIR/*.* %BuildRoot/%INSTALL_DIR

%files
%INSTALL_DIR
%defattr(-,%OWNER,%OWNER,-)

%changelog


最後,我打包成一個 bash script 來筆記 :p 有需要可以試試看,但要留意 bash script 有刪檔的動作,若擔心可以先把刪檔的動作註解起來:

$ git clone https://github.com/changyy/rpm-builder
$ cd rpm-builder
$ bash rpm_build_for_files_archive.sh
Usage> bash rpm_build_for_files_archive.sh SOURCE_DIR RPM_OUTPUT_DIR RPM_PACKAGE_NAME RPM_PACKAGE_VERSION RPM_PACKAGE_VERSION RPM_PACKAGE_INSTALL_DIR

$ bash rpm_build_for_files_archive.sh /etc/yum.repos.d/ /tmp test-yum 1.0 1 /opt


產出 /tmp/test-yum-repos-1.0-1.noarch.rpm ,會安裝在 /opt/test-yum 目錄

最後提一下 rpm 相關指令,方便 debug 用途:
  • 安裝
    • $ rpm -ivh your.rpm
  • 解除安裝 
    • $ rpm -e your_package_name
  • 查詢描述資料
    • $ rpm -qi your_package_name
  • 列出 rpm 所安裝的檔案清單
    • $ rpm -q --filesbypkg your_package_name
  • 查詢檔案是屬於哪個 rpm 
    • $ rpm -qf /opt/path/filename
  • 列出所有已安裝的 rpm 清單,可搭配 grep 挑選關鍵字出來
    • $ rpm -qa

2015年11月2日 星期一

[Linux] 透過 yum 安裝 Nginx 官方版 @ CentOS 6.6、nginx-1.8.0

最近在整理 yum server 才驚覺 nginx 沒有在 CentOS package 裡頭,同理 tmux 也是 Orz

$ vim /etc/yum.repos.d/nginx.repo
name=nginx repo
baseurl=http://nginx.org/packages/centos/6/$basearch/
gpgcheck=0
enabled=1

$ yum update
$ yum search nginx
Loaded plugins: security
============================================================= N/S Matched: nginx =============================================================
nginx-debug.x86_64 : debug version of nginx
nginx-debuginfo.x86_64 : Debug information for package nginx
nginx-nr-agent.noarch : New Relic agent for NGINX and NGINX Plus
nginx.x86_64 : High performance web server

$ yum install nginx
$ yum info nginx
Installed Packages
Name        : nginx
Arch        : x86_64
Version     : 1.8.0
Release     : 1.el6.ngx
Size        : 872 k
Repo        : installed
From repo   : nginx
Summary     : High performance web server
URL         : http://nginx.org/
License     : 2-clause BSD-like license
Description : nginx [engine x] is an HTTP and reverse proxy server, as well as
            : a mail proxy server.


對照 nginx.org 官方版本公告,其中 nginx-1.8.0 是 2015/04/21 釋放出的穩定版。