Google+ Followers

2015年11月27日 星期五

DevOps 筆記 - 使用 Jenkins、Git、RPM、Ansible、Slack 建置持續整合架構(Continuous integration)

今天終於端出一盤比較好吃的菜了,用料有點雜筆記一下吧!之前已經用 Jenkins + Git + Slack 可以做到持續發布跟通知開發者了,但面對 AWS Auto Scaling 議題還是頗抖的,因為建立 AMI (Amazon Machine Image) 後,機器的狀態被保留在某一刻,這時服務量一大自動開啟機器時,就會出現服務更新的需求。原先同事採用 Puppet 方案,但仍不適合處理 Auto Scaling 的問題,因為 Puppet 需要比較強的關係式(Server/Client認證),雖然硬做也是 ok 的,但要一直維護 Server/Client 架構還是頗累,所幸有了 Ansible 的出現!這提供比較分散式的管理架構,Server 跟 Client 都是透過 keypair + ssh remote command 溝通的。

粗略配置作法:
  • Git
    • 主要特色還是 branch 的規劃,跟公司文化有關
      • master:正式網站的狀態
      • alpha:內部最新網站狀態
      • develop:最新程式碼
    • 開發者都是從 develop branch 開發服務,當有版本要釋出時,merge 進 alpha branch 後,發佈到 alpha site 測試,妥當後再把 alpha branch merge 進 master branch
      • 概念上有點從 develop -> alpha -> master 角度
      • 若有 hotfix 時,從 master 產生 branch 修正後,在 merge 回 alpha 跟 develop
      • 在 alpha site 測試出問題時,不斷地在 alpha branch 處理,到最後才 merge 回 develop branch,以及發佈到 master branch
    • 透過 Jenkins 每一次發版時,會下 git tag 標記狀態
      • develop-version-number
      • alpha-version-number
      • production-version-number
    • 相關資料:A successful Git branching model
  • Jenkins 
    • 新增 Slack Notification Plugin/Version Number Plug-In
    • 權限設定,為了不要暴露太多專案給開發者,做了簡易的權限管控
      • 設定全域安全性
        • 安全性領域:採用 Jenkins 內建使用者資料庫 且不允許註冊
        • 授權:採用專案型矩陣授權策略
          • 每一個 task 都可以自訂可以觀看的使用者
    • 管理方面,新增工作分成 build 跟 deploy 兩種
      • build 就是把 source code 包成 RPM ,方便作業系統管理和除錯追蹤,另外,對於開發者測試機的準備也有幫助
        • 取得最新程式碼後,透過 RPMBuild 包裝起來,擺至指定位置
        • 每一次 build 都會下 git tag 標記版本狀態,方便之後除錯
        • 若封裝過程可以接觸到 git 資訊,則把 source code 最後 3 筆 commit log 拉出來製作版本資訊
          • git log -3 --pretty=format:"{\"commit\":\"%H\",\"update\":\"%ad\",\"author\":\"%an\"}"
      • deploy 就是透過 ansible 進行發佈,因此也可以把 ansible 操作用 git 管理
        • 發布過程中,第一步先到指定位置找出最新的 RPM files
        • 第二部透過 AWS EC2 API 得知待更新機器列表
        • 透過 ssh remote command 進行機器更新
        • 盡量把所有動作濃縮至 Ansible command,而透過 Jenkins Web UI 撰寫幾個指令就好 
      • 工作通知
        • 每一次 build 或 deploy 都可以透過 slack plugin 將更新訊息發佈至指定頻道通知相關開發者
      • 採用輪詢 SCM 方式,由 Jenkins 主動確認程式碼狀態,以此決定是否出 build ,不採用 git hook 等被動方式進行工作任務
        • 無法規範開發者 git push 行為,擔心 push 太多造成大量 build event
      • build 完畢後,可以設立相關專案,自動進行 deploy 任務
  • AWS
    • 透過 AIM 建立一個只有 AmazonEC2ReadOnlyAccess 權限的帳號
    • 對於需被管理的機器,都透過 Tag 標記管理
      • 使用 AWS EC2 API 來動態查詢指定的機器,以此取得機器列表,例如指定 ELB 裡所有的機器
    • 關於 Auto Scaling
      • 由於機器 IP 非固定,在不同 data center 溝通容易踩到 Security Group 限制,建議還是把服務以 data center 切開
        • 例如有美西跟日本時,若 db server 開在美西,那需要存取此 db server 的機器就擺在美西就好,這樣 Security Group 比較好做(類似限制 LAN 即可)
  • Ansible
    • 將 RPM 複製到機器上安裝,在 Ubuntu server 可偷懶用 alien :P
    • 依照腳本,進行機器的環境配置,如 limits、web server 等
    • 埋入一個 boot script ,當開機時透過 ssh remote command 向 deploy server 要求更新自己
以上使用的相關資源:
  • RPM 打包目錄專用
    • https://github.com/changyy/rpm-builder
    • 若打包目錄有 .git 時,會抓出最後幾筆 commit log 塞進 version.json 檔案內,可協助往後的偵錯
    • 由於 RPMBuild 很吃環境變數,面對 Jenkins 同時出 build 會衝到共用資源的部分,在此已經透過修改 $HOME 目錄來避開共用資源的問題,因此不用再擔心 Jenkins 一次只能出一個 build
  • AWS EC2 機器查詢
    • http://docs.ansible.com/ansible/intro_dynamic_inventory.html#example-aws-ec2-external-inventory-script
    • Jenkins 建置範例(script),只要修改 yml 檔案名稱即可:
      • develop site
        • export ANSIBLE_HOST_KEY_CHECKING=False
          export EC2_INI_PATH=$WORKSPACE/dynamic-inventory-ec2.ini
          export AWS_ACCESS_KEY_ID=XXX
          export AWS_SECRET_ACCESS_KEY=OOO
          export DEPLOY_TARGET=site-develop.yml

          ansible-playbook $WORKSPACE/$DEPLOY_TARGET -i $WORKSPACE/bin/ec2.py --private-key=$HOME/.ssh/ansible-deploy.pem
      • alpha site
        • export ANSIBLE_HOST_KEY_CHECKING=False
          export EC2_INI_PATH=$WORKSPACE/dynamic-inventory-ec2.ini
          export AWS_ACCESS_KEY_ID=XXX
          export AWS_SECRET_ACCESS_KEY=OOO
          export DEPLOY_TARGET=site-alpha.yml

          ansible-playbook $WORKSPACE/$DEPLOY_TARGET -i $WORKSPACE/bin/ec2.py --private-key=$HOME/.ssh/ansible-deploy.pem
      • production site
        • export ANSIBLE_HOST_KEY_CHECKING=False
          export EC2_INI_PATH=$WORKSPACE/dynamic-inventory-ec2.ini
          export AWS_ACCESS_KEY_ID=XXX
          export AWS_SECRET_ACCESS_KEY=OOO
          export DEPLOY_TARGET=site-production.yml

          ansible-playbook $WORKSPACE/$DEPLOY_TARGET -i $WORKSPACE/bin/ec2.py --private-key=$HOME/.ssh/ansible-deploy.pem
  • Ansible 找尋最新 package files
  • Ansible 設置自動要求更新
    • $ ansible-galaxy install changyy.self-update 
    • https://github.com/changyy/ansible-role-self-update
    • 比較雜亂一點,原理:
      • 機器部署時,建立一個 auto update script (負責通報 deploy server 更新指定 host),將此 script 埋入開機執行區( /etc/rc.local )
        • 當機器啟動時執行該 script ,把部署此機器該有的資訊以 ssh remote command 丟給 deploy server 
      • 該 ssh remote command 包含的動作
        • 登入 deploy server 並切換至 ansible workspace
        • 透過 dynamic inventory 建立動態 host 資訊
        • 執行 ansble-playbook 批次指令
      • Deploy server workspace 範例:
        • $ cat bin/echo.sh
          #!/bin/bash
          echo "{\"$HOST\":$DATA}"

          $ HOST=MySite DATA=[\"SERVER_IP\"] bash bin/echo.sh
          {"MySite":["SERVER_IP"]}

          $ cd ansible-deploy-dir && \
          ANSIBLE_HOST_KEY_CHECKING=false \
          HOST=MySite \
          DATA=[\"SERVER_IP\"] \
          ansible-playbook MySite.yml \
          -i bin/echo.sh \
          --private-key=mysite-login-key.pem

2015年11月26日 星期四

Ansible 筆記 - 讓 Server 主動更新的方式

這陣子使用 Ansible 進行大部分的機器發佈,也進入了另一個瓶頸:如何讓 Server 自動啟動後也能更新至最新版。我把它當作 Server 主動更新的流程。

目前搭配 Jenkins + git,可以定期追蹤是否要出新版,一旦決定出新版後,就是一道道 Ansible 指令執行完成發佈。在架設 AWS Auto Scaling 時,將碰到服務量變大時,將會自動開啟機器,此時這個機器狀態是有機會是過時的,這時需要讓這些機器開啟啟動時進行軟體更新,來確保得到的資料是最新的。

透過 Dynamic Inventory 架構,仿造 AWS EC2 External Inventory Script 來執行,動態塞 host 資訊給 Ansible 即可,例如:

$ cat bin/echo.sh
#!/bin/bash
echo "{\"$HOST\":$DATA}"

$ HOST=target DATA=[\"server_ip1\",\"server_ip2\"] bash bin/echo.sh                                                
{"target":["server_ip1","server_ip2"]}

$ ANSIBLE_HOST_KEY_CHECKING=false HOST=webserver DATA=[\"localhost\",\"127.0.0.1\"] ansible webserver -i bin/echo.sh -m raw -a date -k
SSH password:
127.0.0.1 | success | rc=0 >>
Thu Nov 25 11:23:26 UTC 2015


localhost | success | rc=0 >>
Thu Nov 25 11:23:26 UTC 2015


因此,整個設計架構就是讓 servers 帶足資訊到發動 Deploy(ansible commands) server 請他更新自己即可!

2015年11月25日 星期三

[PHP] 尚未設定 phpMyAdmin 資料庫,某些進階功能將無法使用 @ Ubuntu 14.04

很久沒用 PHPMyAdmin 了,因為是都改用 Sequel Pro 啦!但開放給同事還是要用 PHPMyAdmin 就是了。簡言之,架設 PHPMyAdmin 後,在 /etc/phpmyadmin/config.inc.php 有個 controluser 跟 controlpass 要設定,之前都是偷懶用 root 權限,這次仔細研究一下,其實只要讓 controluser 可以管理 phpmyadmin database 即可,要限縮權限可以限縮到只能看到 phpmyadmin 資料庫!

至於 phpMyAdmin 資料庫未建立的,可以用以下招數進行處理:

$ ls /usr/share/doc/phpmyadmin/examples/create_tables.sql.gz
$ cp /usr/share/doc/phpmyadmin/examples/create_tables.sql.gz . && gunzip create_tables.sql.gz
$ mysql -h hostname -u controluser -p < create_tables.sql


如此一來,就會建立 phpmyadmin 資料庫,以及一堆 phpmyadmin.pma__* 開頭的資料表。

此外,如果在 AWS RDB 開很多 DB server 時,可以偷懶在 /etc/phpmyadmin/config.inc.php 添加位置,並且做完上述動作後,大家就可以透過 PHPMyAdmin 連線使用了!

$cfg['Servers'][$i] = $cfg['Servers'][$i-1];
$cfg['Servers'][$i]['host'] = 'new-aws-db-server-hostname';
$cfg['Servers'][$i]['controluser'] = 'phpmyadmin-pma-controller';
$cfg['Servers'][$i]['controlpass'] = 'password';
$i++;

iOS 開發筆記 - 把玩 Chromecast SDK 播放網路連結(圖片、影片)

買了一隻 Chromecast ,除了試用產品外,也想試試看 Chromecast SDK 。快速翻了一下 iOS App Development - ios sender 文件,還是不得其門而入 XD 最後還是乖乖看範例程式:https://github.com/googlecast/CastVideos-ios

簡言之,我是使用 Cocoapods 管理 Chromecast SDK 的:

$ vim Podfile
pod 'google-cast-sdk'
pod 'google-cast-remote-display-sdk'


接著寫一點程式,參考 CastVideos-ios 內的 CastDeviceController 範例了解整個流程。

總之,若要簡單播放一個 URL ,需要依序完成以下動作:

  1. 搜尋 chromecast device 並實作 GCKDeviceScannerListener 偵測裝置
    • GCKFilterCriteria *filterCriteria = [GCKFilterCriteria criteriaForAvailableApplicationWithID:kGCKMediaDefaultReceiverApplicationID];
    • self.chromecastScanner = [[GCKDeviceScanner alloc] initWithFilterCriteria:filterCriteria];
    • [self.chromecastScanner addListener:self];
    • [self.chromecastScanner startScan];
  2. 透過 - (void)deviceDidComeOnline:(GCKDevice *)device 偵測到後,進行連線
    • self.chromecastManager = [[GCKDeviceManager alloc] initWithDevice:self.chooseDevice clientPackageName:[NSBundle mainBundle].bundleIdentifier];
    • self.chromecastManager.delegate = self;
    • [self.chromecastManager connect];
  3. 連線裝置後,透過 Launch application 跟 Chromecast Device 進行溝通
    • [self.chromecastManager launchApplication:kGCKMediaDefaultReceiverApplicationID];
  4. 透過 - (void)deviceManager:(GCKDeviceManager *)deviceManager didConnectToCastApplication:(GCKApplicationMetadata *)applicationMetadata sessionID:(NSString *)sessionID launchedApplication:(BOOL)launchedApplication; 偵測完成 chromecast app 程式啟動並建立 GCKMediaControlChannel
    • self.mediaControlChannel = [[GCKMediaControlChannel alloc] init];
    • self.mediaControlChannel.delegate = self;
    • [deviceManager addChannel:self.mediaControlChannel];
    • [self.mediaControlChannel requestStatus];
  5. 播放 Resource,以 http://www.w3schools.com/html/mov_bbb.mp4 為例
    • NSMutableArray *tracks = [[NSMutableArray alloc] init];
    • GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc] initWithMetadataType:GCKMediaMetadataTypeGeneric];
    • GCKMediaInformation *mi = [[GCKMediaInformation alloc] initWithContentID:@"http://www.w3schools.com/html/mov_bbb.mp4" streamType:GCKMediaStreamTypeNone contentType:nil metadata:metadata streamDuration:0 mediaTracks:tracks textTrackStyle:[GCKMediaTextTrackStyle createDefault] customData:nil];
    • [self.mediaControlChannel loadMedia:mi autoplay:YES];

如此一來 Chromecast device 就會播放指定的連結啦。

2015年11月18日 星期三

Ansible 筆記 - 透過 find-package 找到指定安裝檔進行遠端部署 @ Ubuntu 14.04

手癢貢獻一個 ansible role 出去:changyy.find-package 。透過 find-package 功能,可以將包裝好的 package 複製到遠端機器安裝,這個方式可以偷懶省去架設 package server (例如 Yum Server)。

以下是簡易的範例,透過 unix find 指令,找尋剛製作好的 package ,把它記錄在變數中:

$ mkdir -p ansible-test/roles && cd ansible-test && ansible-galaxy install changyy.find-package -p roles/
- downloading role 'find-package', owned by changyy
- downloading role from https://github.com/changyy/ansible-role-find-package/archive/master.tar.gz
- extracting changyy.find-package to roles/changyy.find-package
- changyy.find-package was installed successfully

$ vim test.yml
---
- hosts: localhost
  roles:
  - { role: changyy.find-package, package_dir: "/data/rpm/production/" , package_name_prefix: "package-*" }

- hosts: 127.0.0.1

  tasks:
  - name: copy package
    copy: src="{{hostvars['localhost']['package_path']}}" dest=/tmp

$ ansible-playbook test.yml


連續動作還可以發佈至指定機器上,詳情請見:https://github.com/changyy/ansible-role-find-package

Ansible 筆記 - 找尋本機端資源並部署至遠端機器,包含複雜 shell 指令、資源尋找錯誤處理、hosts 之間變數傳遞等

這個需求其實還滿簡單實現的,但為了想要達成更仔細的錯誤偵測,後來越改越複雜 XD 順便筆記一下。完整的使用情境:每次部署遠端機器前,本地端會有一個 package 產出(rpm),透過 find 指令找到最新產出的 package 後,再部署到遠端機器(其實也可以把架設 yum server 來搞定啦 XD)

最簡單的方式,使用 vars 紀錄:

- hosts: YourTargetServers

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "
    rpm_path: "{{ lookup('pipe', 'rpm_find_command') }}"

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      debug: msg="{{rpm_path}}"


這樣算是收工了,但是 rpm_path 若沒找到時,卻無法有洽當的錯誤偵測 Orz

所以第一次再改成這招:

- hosts: YourTargetServers

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      local_action: command /bin/bash -c "{{ rpm_find_command }}"
      sudo: False
      failed_when: " '' != rpm_path_result.stderr"

    - name: get rpm path
      debug: msg="{{ rpm_path_result.stdout }}"


但這樣仍有個缺點,那就是若有 roles 等一堆工作,會變成那堆工作做完才會跑 tasks,這時就會希望先偵測本地端的資料在跑 roles 工作,於是乎又改成:

- hosts: loclahost

  vars:
    rpm_dir: "/data/rpm/production/"
    rpm_name_prefix: "packagename*"
    rpm_find_command: "find {{ rpm_dir }} -name '{{ rpm_name_prefix }}' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d ' ' "

  tasks:
    - name: find rpm prefix name
      debug: msg="{{rpm_name_prefix}}"

    - name: find rpm command
      debug: msg="{{rpm_find_command}}"

    - name: find rpm path
      local_action: command /bin/bash -c "{{ rpm_find_command }}"
      failed_when: " '' != rpm_path_result.stderr"

    - name: get rpm path
      debug: msg="{{ rpm_path_result.stdout }}"

- hosts: YourTargetServers

  vars:
    rpm_path: "{{ hostvars['localhost']['rpm_path_result']['stdout'] }}"

  tasks:
    - name: find rpm path
      debug: msg="{{rpm_path}}"


如此一來,就可以先確保在 localhost 把資源準備齊了,再進行遠端部署動作,也包含偵錯處理啦。

2015年11月17日 星期二

Ansible 筆記 - 動態取得 EC2 機器進行發佈更新 (Dynamic Inventory) @ Ubuntu 14.04

摸了好一陣子,終於搞懂了 :P 簡單的說,採用 Dynamic Inventory 進行動態取得機器列表,此例是透過 EC2 相關工具來取得的。

$ echo `EC2_INI_PATH=ec2.ini AWS_ACCESS_KEY_ID=KEY_ID AWS_SECRET_ACCESS_KEY=ACCESS_KEY python ec2.py --refresh-cache`
{ "_meta": { "hostvars": { "IP" : { ... } } } }


其中 ec2.ini 內容:

[ec2]

#https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/ec2.ini
#regions = all
regions = ap-northeast-1
regions_exclude =

destination_variable = public_dns_name
vpc_destination_variable = ip_address
route53 = False

cache_path = /tmp
cache_max_age = 0

rds = False
elasticache = False

#instance_filters = tag:Ansible=*


整體上,執行 ansible-playbook 方式就改成:

$ ANSIBLE_HOST_KEY_CHECKING=false EC2_INI_PATH=ec2.ini AWS_ACCESS_KEY_ID=KEY_ID AWS_SECRET_ACCESS_KEY=ACCESS_KEY ansible-playbook mysite.yml -i ec2.py --private-key=ansible-deploy.pem

並且在 mysite.yml 中定義的 hosts 也需調整,例如想要透過 EC2 上的 tag 資訊,例如我對 EC2 上的機器標記 Tag 為 Ansible=deploy 機器發佈,這時可以先透過 python ec2.py 觀看想要的 tag 被轉成什麼,接著再去修改 mysite.yml 的 hosts 資訊即可。而 ANSIBLE_HOST_KEY_CHECKING=false 是用在略過 ssh key checking

例如:

$ echo `EC2_INI_PATH=ec2.ini AWS_ACCESS_KEY_ID=KEY_ID AWS_SECRET_ACCESS_KEY=ACCESS_KEY python ec2.py --refresh-cache`
{ "_meta": { "hostvars": { "IP" : { ... } } }, "tag_Ansible_deploy": [ "IP" ] }

$ cat mysite.yml
- hosts: tag_Ansible_deploy
  ...


最後一提,此例使用的 ec2.py 是從官方文件提到的,執行時需安裝 python-boto 環境,此例為了搭配 Jenkins 使用,盡可能把執行 AWS API 所需的資料都透過環境變數傳遞。

2015年11月13日 星期五

Ansible 筆記 - 建立自己的 roles ,以架設 Nginx 為例 @ Ubuntu 14.04

目前的目錄結構:

$ tree
.
├── add_authorized_key.yml
├── ansible-deploy.pem
├── ansible-deploy.pub
├── etc
│   └── ansible
│       └── hosts
└── mysite.yml

$ mkdir -p roles && cd roles && ansible-galaxy init nginx && cd - && tree .
- nginx was created successfully
.
├── add_authorized_key.yml
├── ansible-deploy.pem
├── ansible-deploy.pub
├── etc
│   └── ansible
│       └── hosts
├── roles
│   └── nginx
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       ├── handlers
│       │   └── main.yml
│       ├── meta
│       │   └── main.yml
│       ├── README.md
│       ├── tasks
│       │   └── main.yml
│       ├── templates
│       └── vars
│           └── main.yml
└── mysite.yml

11 directories, 11 files


開始撰寫 mysite.yml 跟 nginx 相關資料:

$ cat mysite.yml
---
- hosts: mysite
  remote_user: ubuntu
  sudo: yes

  roles:
    - nginx

  tasks:
    - name: ubuntu system upgrade
      apt: upgrade=dist

$ ansible-playbook mysite.yml -i etc/ansible/hosts --private-key=ansible-deploy.pem

PLAY [mysite] **************************************************

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

TASK: [ubuntu system upgrade] *************************************************
ok: [XX.XX.XX.XX]

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


代表目標機器已經更新好系統了,而剛剛添加的 roles/nginx 目前還是個空殼所以沒有任何動作。

接著要研究一下 Nginx 官方安裝位置:http://nginx.org/packages 和 http://nginx.org/en/linux_packages.html 對照表。此例需要添加官方 repo 的原始指令方式:

$ vim /etc/apt/sources.list
deb http://nginx.org/packages/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/ubuntu/ trusty nginx


指令方式:

$ echo "deb http://nginx.org/packages/ubuntu/ trusty nginx" | sudo tee -a /etc/apt/sources.list
$ echo "deb-src http://nginx.org/packages/ubuntu/ trusty nginx" | sudo tee -a /etc/apt/sources.list
$ sudo apt-get update


而在 ansible 則是透過:

- name: Add Nginx Official Repo - deb
  apt_repository: repo='deb http://nginx.org/packages/ubuntu/ {{ ansible_distribution_release }} nginx' state=present

- name: Add Nginx Official Repo - deb-src
  apt_repository: repo='deb-src http://nginx.org/packages/ubuntu/ {{ ansible_distribution_release }} nginx' state=present

# http://docs.ansible.com/ansible/apt_key_module.html
- name: Add Nginx Official Repo - package signing key
  apt_key: url=http://nginx.org/packages/keys/nginx_signing.key state=present

#- name: Add Nginx Official Repo - ppa:nginx/stable
#  apt_repository: repo='ppa:nginx/stable'

- name: Update cache
  apt: update_cache=yes

- name: Install Nginx
  apt:
    pkg: nginx
    state: installed


到現在為止,整個 nginx role 的安裝設定:

$ tree .
.
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   ├── main.yml
│   └── task-Ubuntu.yml
├── templates
└── vars
    └── main.yml

7 directories, 7 files

$ cat tasks/main.yml
---
# tasks file for nginx

- include: task-Ubuntu.yml
  when: ansible_distribution == 'Ubuntu'

$ cat tasks/task-Ubuntu.yml
---
# http://docs.ansible.com/ansible/apt_repository_module.html
- name: Add Nginx Official Repo - deb
  apt_repository: repo='deb http://nginx.org/packages/ubuntu/ {{ ansible_distribution_release }} nginx' state=present

- name: Add Nginx Official Repo - deb-src
  apt_repository: repo='deb-src http://nginx.org/packages/ubuntu/ {{ ansible_distribution_release }} nginx' state=present

# http://docs.ansible.com/ansible/apt_key_module.html
# $ curl -s 'http://nginx.org/packages/keys/nginx_signing.key' | sudo apt-key add -
- name: Add Nginx Official Repo - package signing key
  apt_key: url=http://nginx.org/packages/keys/nginx_signing.key state=present

#- name: Add Nginx Official Repo - ppa:nginx/stable
#  apt_repository: repo='ppa:nginx/stable'

- name: Update cache
  apt: update_cache=yes

- name: Install Nginx
  apt:
    pkg: nginx
    state: installed


其中 ansible_distribution_release, ansible_distribution 環境變數,可以透過 ansible -i host mysite -m setup 得知。接著再次執行 ansible-playbook ,過程就會安裝 nginx 啦(changed=4 都落在 nginx role 動作) :

$ ansible-playbook mysite.yml -i etc/ansible/hosts --private-key=ansible-deploy.pem

PLAY [mysite] **************************************************

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

TASK: [nginx | Add Nginx Official Repo - deb] *********************************
changed: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - deb-src] *****************************
changed: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - package signing key] *****************
changed: [XX.XX.XX.XX]

TASK: [nginx | Update cache] **************************************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Install Nginx] *************************************************
changed: [XX.XX.XX.XX]

TASK: [ubuntu system upgrade] *************************************************
ok: [XX.XX.XX.XX]

PLAY RECAP ********************************************************************
XX.XX.XX.XX             : ok=7    changed=4    unreachable=0    failed=0


最後要客製化 Nginx 的話,就變成要改寫設定檔了,改寫設定檔前,當然要來個設定檔測試,這時規劃在 roles/nginx/handlers/main.yml 設定一些事件處理:

$ cat handlers/main.yml
---
# handlers file for nginx

- name: set nginx auto start
  service: name=nginx state=started enabled=yes

- name: restart nginx
  service: name=nginx state=restarted

- name: test nginx config
  shell: service nginx configtest
  register: result
  changed_when: "result.rc != 0"
  always_run: yes


接著回到 mysite.yml 設定檔,可以添加:

- hosts: mysite
  remote_user: ubuntu
  sudo: yes

  roles:
    - nginx

  tasks:
    - name: ubuntu system upgrade
      apt: upgrade=dist update_cache=yes

    - name: ensure nginx auto start
      service: name=nginx state=started enabled=yes

    - name: test webserver configure files
      always_run: yes
      shell: date
      notify: test nginx config


如此一來,除了安裝 nginx 、更新系統外,還會多了測試 nginx configure files:

$ ansible-playbook mysite.yml -i etc/ansible/hosts --private-key=ansible-deploy.pem

PLAY [mysite] **************************************************

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

TASK: [nginx | Add Nginx Official Repo - deb] *********************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - deb-src] *****************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - package signing key] *****************
ok: [XX.XX.XX.XX]

TASK: [nginx | Update cache] **************************************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Install Nginx] *************************************************
ok: [XX.XX.XX.XX]

TASK: [ubuntu system upgrade] *************************************************
ok: [XX.XX.XX.XX]

TASK: [ensure nginx auto start] ***********************************************
ok: [XX.XX.XX.XX]

TASK: [test webserver configure files] ****************************************
changed: [XX.XX.XX.XX]

NOTIFIED: [nginx | test nginx config] *****************************************
ok: [XX.XX.XX.XX]

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


這邊發動測試 nginx 測試檔是綁定在一個 date 指令上,因為 date 每次結果都不一樣,將導致永遠都是 changed=1 結果。以上就完成了建立一個 role 並且納入使用的方式。

接著試試設定的部分吧!把一些設定檔那來做模板(templates),未來可以透過引入 nginx role 時,搭配指定參數後,即可產生客製化的設定檔:

先將機器上的 nginx 設定檔搬到 roles/nginx/templates 中,且檔案結尾改成 .j2 ,這是因為 ansible template 採用 Jinja2 方案。

$ scp -r -i ansible-deploy.pem [email protected]:/etc/nginx/conf.d  roles/nginx/templates/
$ cp roles/nginx/templates/conf.d/default.conf roles/nginx/templates/default.conf.j2
$ tree roles/nginx/templates/
roles/nginx/templates/
├── conf.d
│   ├── default.conf
│   └── example_ssl.conf
└── default.conf.j2


之後就是把機器上的 default.conf 覆蓋掉或是刪掉並搭配新增一則 mysite.conf 來啟用 nginx 了,在此先沿用覆蓋方式。

接著在 mysite.yml 或是 roles/nginx/task/task-Ubuntu.yml 中,就可以引入從 template 取得資料來蓋掉系統檔案,若在 mysite.yml 中:

- name: Update Nginx configure files
  template: src=roles/nginx/templates/default.conf.j2 dest=/etc/nginx/conf.d/default.conf


在 roles/nginx/task/task-Ubuntu.yml:

- name: Update Nginx configure files
  template: src=default.conf.j2 dest=/etc/nginx/conf.d/default.conf


通常都會寫在 roles/nginx/task/main.yml 為多,而上述是因為有偵測系統狀況決定 nginx 設定檔位置,因此我一樣寫在 task-Ubuntu.yml 中。接下來可以朝改寫 default.conf.j2 內變數地用法,將一些常見數值改成變動的,並且在 roles/nginx/defaults/main.yml 定義好,如此一來,在 mysite.yml 引用 roles nginx 時,也就可達成動態變動的方式。

例如:

$ tree .
.
├── ansible-deploy.pem
├── ansible-deploy.pub
├── etc
│   └── ansible
│       └── hosts
├── roles
│   └── nginx
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       ├── handlers
│       │   └── main.yml
│       ├── meta
│       │   └── main.yml
│       ├── README.md
│       ├── tasks
│       │   ├── main.yml
│       │   └── task-Ubuntu.yml
│       ├── templates
│       │   ├── conf.d
│       │   │   ├── default.conf
│       │   │   └── example_ssl.conf
│       │   └── default.conf.j2
│       └── vars
│           └── main.yml
└── mysite.yml

$ cat roles/nginx/templates/default.conf.j2
server {
    listen       {{ nginx_listen }};
    server_name  {{ nginx_server_name }};

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    location / {
        root   {{ nginx_document_root }};
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}


提供了 nginx_listen、nginx_server_name 和 nginx_document_root 三個變數,並且定義預設數值:

$ cat roles/nginx/defaults/main.yml
---
# defaults file for nginx
nginx_listen: 80
nginx_server_name: localhost
nginx_document_root: /usr/share/nginx/html


如此一來,在 mysite.yml 中,引入 roles 可以改成:

$ cat mysite.yml
---
- hosts: mysite
  remote_user: ubuntu
  sudo: yes

  roles:
    - { role: 'nginx', nginx_listen: 8080 }

  tasks:
    - name: ubuntu system upgrade
      apt: upgrade=dist update_cache=yes

    - name: test webserver configure files
      always_run: yes
      shell: date
      notify: test nginx config

    - name: reload webserver configure files
      always_run: yes
      shell: date
      notify: restart nginx


透過 ansible-playbook mysite.yml 後,可以去該 server:/etc/nginx/conf.d/default.conf 觀看到數值啦

$ ansible-playbook mysite.yml -i etc/ansible/hosts --private-key=ansible-deploy.pem
PLAY [mysite] **************************************************

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

TASK: [nginx | Add Nginx Official Repo - deb] *********************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - deb-src] *****************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Add Nginx Official Repo - package signing key] *****************
ok: [XX.XX.XX.XX]

TASK: [nginx | Update cache] **************************************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Install Nginx] *************************************************
ok: [XX.XX.XX.XX]

TASK: [nginx | Update Nginx configure files] **********************************
ok: [XX.XX.XX.XX]

TASK: [ubuntu system upgrade] *************************************************
ok: [XX.XX.XX.XX]

TASK: [ensure nginx auto start] ***********************************************
ok: [XX.XX.XX.XX]

TASK: [test webserver configure files] ****************************************
changed: [XX.XX.XX.XX]

TASK: [reload webserver configure files] **************************************
changed: [XX.XX.XX.XX]

NOTIFIED: [nginx | restart nginx] *********************************************
changed: [XX.XX.XX.XX]

NOTIFIED: [nginx | test nginx config] *****************************************
ok: [XX.XX.XX.XX]

PLAY RECAP ********************************************************************
XX.XX.XX.XX             : ok=13   changed=3    unreachable=0    failed=0

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 檔案添加至 [email protected] 的 ~/.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: [email protected]: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 釋放出的穩定版。