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 把資源準備齊了,再進行遠端部署動作,也包含偵錯處理啦。