今天追完了綠箭, 該來點功課, 不過今天忘了買啤酒了:(

Parse Push, 這功能無疑應該是Parse本身比較受歡迎的部分, 既然Parse明年要關門大吉了,這部份也是必須要搬家的, 但搬家比較麻煩的不是要搬到哪去, 而是之前累積的使用者, 總不希望一搬了就找不到他們了, Parse Push支援了包含了Google的GCM和Apple的APNS,以及他們自己的PPNS,以GCM來說, 在_Installation裡面, Parse是有儲存了GCM的deviceToken, 如果你之前用的是自己的GCM Sender ID, 那這部份直接搬了就可以用了, 但如果不是, 那預設用的是Parse自己的, 這樣這些deviceToken就不能夠使用自己的Sender ID來送, 必須要用用下面的方法來補救:

  • 從Google的Developer console取的Sender ID跟API Key
  • 在AndroidManifest.xml加入 <meta-data android:name="com.parse.push.gcm_sender_id" android:value="id:12345678"> (必須要以"id:“加上你的sender id)
  • 發布新的升級到Play store
  • 使用者更新後至少發送一次Push (如果你在Application中有去儲存Installation, 這時候因為有了新的sender, 所以deviceToken會被更新)
  • 可以用你自己的API key送給新的device token了(可以不用透過Parse console去送, 也可以用其他程式)

這個Go的範例可以用來試驗送PUSH到你的手機上:

找出你手機的deviceToken後即可用這個來驗證是否能夠成功送出push

但這樣還不夠, 我們還需要一個負責幫我們送push的server, 在之前那篇Parse自救方案裡有提到一個Pushd, 這是其中一個作法, 不過需要改一些東西才可以符合Parse Android SDK接收的格式, 另一個作法是透過open source的Parse Server, 原本它剛一出來時, 並沒支援Push, 但後來有其他人幫它加了上去了, 但它的方式是提供了API給你送PUSH, 但一來把API server跟PUSH server放一起並不是件好事, 二來當送大量的PUSH這樣的設計並不保險(並沒有queue, 送到一半server死了就麻煩了)

不過要以Parse server的為基礎拆出push的部分也不是很難, 做了一個簡單的範例: github.com/julianshen/parse_push_js

在這範例裡, 使用了Parse Server的ParsePushAdapter, 並且利用了Redis當作queue來做成的簡單的push server, 這樣應該就加減堪用了, 不過這邊有一個還需要再改的是, ParsePushAdapter 並不會把產生的push id回傳到上一層, 這樣要做追蹤紀錄其實有點比較不方便, 這邊還需要再加強一下

前篇 裝好了MongoDB的Cluster後(1 Primary, 2 Secondaries)就開始進行大量的資料移轉

結果資料寫到一半, 突然發現Primary換人了, 雖然因為有Secondaries, 會有人上來替代, 因為Mongo只有Primary才可以被寫入, 這使得client必須重新建立對新的primary的connection, 一度以為機器被reboot了, 但查logs並沒這現象, 後來又以為, MongoDB也太不濟了吧, 這種量級的寫入居然可以擊倒它, 結果後來查了log發現, 他的確被restart了, 只是兇手不是他, 是別人叫他去死的

一切是Fleet惹的禍, 根據這篇Fleet engine stops units when etcd leadership change or has connectivity issues #1289, Fleet只要聯絡不到etcd, 就會認為不能獨活了, 就會把其他人也給殺了(可惡的殺人兇手),追根據底就是timeout設的太短了, 以至於當系統稍微(只是稍微而已)一忙, 就很容易超過timeout, 然後他就認為, 他的情人死了!(也太玻璃心了)

解決方案就是延長timeout, 修改cloud-config加上如下的東西(把etcd的heartbeat跟election timeout延長, 把fleet相關的也給延長):


#cloud-config

coreos:
  etcd2:
    heartbeat-interval: 600
    election-timeout: 6000
  fleet:
    engine-reconcile-interval: 10
    etcd-request-timeout: 5
    agent-ttl: 120s

至於Azure上裝的coreos, cloud-config位置是在: /var/lib/waagent/CustomData, 改完restart機器就好

Parse自從宣布要收掉後, 雖然也放出open source的仿製品Parse Server, 這東西不是實際上Parse真正跑得那個app server, 而是一個nodejs/express的相容版, 這個版本的故事可以看這邊 Parse 2.0

想當然爾, 這版本並不是完整支援Parse原本的功能, 尤其是Push, 這功能是完全沒有, 雖然說source codes放出來後, 大家熱烈的玩起來了, 最近幾天一個簡單的PUSH功能的pull request也被收錄進去了: Add support for push, 不過這畢竟比較陽春

一些收費不收費的替代方案也不算少, 不過如果想自救, 自己建置自己管的話, 也是不用完全重來, rs/pushd 這是目前看到還算蠻完整的, 採用了nodejs/coffee script + Redis, 在佈署方面也不算太難(不過也就兩個東西而已), 不過實際上的效能可能還是得測試一下(有沒好方法可以測大量的push呀?總不可能去生一堆機器)

為了方便測試, 包裝成docker-compose.yml和一個方便使用的shell script: julianshen/pushd-docker-compose-sample

使用方法:

  • 修改pushd.sh裡跟你的app相關的push設定如GCM_KEY
  • 執行pushd.sh up啟動redis和pushd兩個containers
  • 執行pushd.sh ps確認是不是兩個都已經正確啟動
  • PUSHD的port是對應到host的8081, 所以直接對8081操作即可

接下來可能要想一些方法來測試, 和把Parse的資料給串一起

把我過年假期試的先來紀錄一下,這篇基本上有兩個部分:

第一部份是在Azure上部署一個coreos cluster,這部份可以參考以下幾篇:

這邊我使用的不是Azure的classic vm而是用了resource manager,這部份 可以參考這篇: Deploy an application with Azure Resource Manager template

要使用arm的話,首先要把模式切到arm:

azure config mode arm

接下來得建立一個resource group:

azure group create -n “group_name” -l “West US”

-n 後面是這個群組的名字, -l 後面則是它所在區域

接下來可以利用resource group templates來建立相關的資源, resource group template 除了可以自己寫以外(參考這邊 Authoring Azure Resource Manager templates), 也可以上Azure 快速入門範本去找現成的, 至於coreos + fleet, 就有一個現成的:Deploy a CoreOS cluster hosting Fleet, 它的source code在github上也可以找到

基本上應該是改一下azuredeploy.parameters.json再執行下面指令就可以:

azure group deployment create GroupName DeploymentName –template-file azuredeploy.json –parameters-file azuredeploy.parameters.json

而裡面的discovery url部分, 則是要去: https://discovery.etcd.io/new?size=3 產生一個貼上去

但我前幾天在試的時候, 似乎它azuredeploy.json的換行有點問題(寫這篇文章時再看它已經是修正了), 以至於我執行起來有點問題, 再加上, 新的coreos stable似乎也改用etcd2取代原本的etcd, 因此我想要用另一個版本的cloud-config(即借用Scaling Docker那段影片裡用的那個cloud-config, 那個即是使用etcd2), 此外他的vm name也全是以coreos開頭的(我想要自己命名)

因此, 我修改了一個自己的版本, 也放在github上: julianshen/azure-core-fleet

這版本把cloud-config獨立成一個template而非寫死在azuredeploy.json, 並借用上面所述的版本, 並且寫了一隻paramgen.go來產生azuredeploy.parameters.json, discovery url不用自己去產生, 這支程式會自動幫你取得, 用法:

  • go get github.com/parnurzeal/gorequest
  • go run paramgen.go –numberOfNodes 3 –location “East US” –newStorageAccountName “mystorageaccount” –vmSize “Standard_A1” –adminUserName “admin” –vmNamePrefix “myvm” –sshKeyFile ~/.ssh/azure_rsa.pub

參數如下:

  • numberOfNodes 要開的vm數量
  • location vm位置(還是得跟你的resource group所在位置一樣)
  • newStorageAccountName storage account name
  • vmSize VM的大小
  • adminUserName 管理者的名稱
  • vmNamePrefix vm名字的開頭
  • sshKeyFile ssk key的檔案位置(如果沒有,預設是~/.ssh/id_rsa.pub)

接著跑前面提到那段:

azure group deployment create GroupName DeploymentName –template-file azuredeploy.json –parameters-file azuredeploy.parameters.json

執行結束後就會得到三頭牛, ㄟ,是三台VM(取決於numberOfNodes數目)及相關資源, 而且依據這個範本, 這三台VM跑得都會是coreos, 擁有我們指定的cloud-config, 這cloud-config會在coreos上啟用etcd2, fleet, 和flanneld (如Scaling Docker那段裡面的一樣)

用ARM的模式的好處是,這三台VM會是在同一個虛擬網路上, 有各自的私有的IP, 當然也有各自的公開IP

裝好後, 有碰到一個問題, 就是不知道為何, 重啟vm時, 會碰到etcd也跑起來了取代了etcd2, 而etcdctl 2.0.9又跟etcd有相容性問題, 導致fleet出問題, 因此在原本的cloud-config加上了:

  • name: etcd.service mask: true

這樣一來etcd就不會去執行了

再來就要測試剛剛的佈署是不是成功沒問題了

首先我們需要fleetctl, 如果不希望登入vm就可以使用, 還是可以在本地端安裝, 在mac下可用

brew install fleetctl

然後要設定兩個環境變數:

  • export FLEETCTL_TUNNEL=xxx.xxx.xxx.xxx
  • export FLEETCTL_SSH_USERNAME=admin (你設定的admin user名字)

接下來用:

fleetctl list-machines –full

會得到像這樣的結果:

MACHINE     IP      METADATA
291c678da14841a7bec4c9aa0dfe168c    10.0.0.6    -
8e63696268c54a62a3f21043669997c6    10.0.0.4    -
fbc2675e0aa940e1b11eb6640b62906f    10.0.0.5    -

這表示這三台vm已經都跑起來了, 如果我們要ssh連上第一台可以用這種方式:

fleetctl ssh 291c678da14841a7bec4c9aa0dfe168c

完全不用知道這台機器的public IP

有了這三台VM, 接下來要做的事就是要deploy Mongodb的replica set了

這邊我找到兩個參考:

第一個其實是參考第二個寫出來的, 這兩個的差異在於第一個使用了data volume container

Data volume container的優缺點及使用方法可以參考這篇: Manage data in containers

本來我想直接採用第一個的, 不過弄了半天, 每次都是在加好admin user後restart mongo時就失敗了, 加上他的方法其實沒辦法保證db container剛好跟data volume container一定在同一台機器(可以參考這一個issue), 後來就有點懶得看, 所以改採用第二個方法

第二個方法還蠻簡單的:

  • 先ssh到任一台(可以用fleetctl ssh),用etcdctl設定這個變數: etcdctl set /mongo/replica/name myreplica
  • 執行 fleetctl start mongo@{1..3}.service 以及 fleetctl start mongo-replica-config.service

在第一個node跑起來後, 它會建立admin user自動產生它的密碼, 建立replica key, 然後再重啟mongodb, 相關參數像是admin user的密碼就會存在etcd, 會需要花一點時間

fleetctl list-units 就可以看各個的執行結果了, 像這樣

UNIT                MACHINE         ACTIVE      SUB
mongo-replica-config.service    8e636962.../10.0.0.4    inactive    dead
[email protected]         8e636962.../10.0.0.4    active      running
[email protected]         291c678d.../10.0.0.6    active      running
[email protected]         fbc2675e.../10.0.0.5    active      running

mongo-replica-config由於只會跑一次, 所以你會看到他最後的狀態是dead

這裡建立出來的admin使用者為 siteRootAdmin , 密碼則可以連上任一台, 用etcdctl取得:

etcdctl get /mongo/replica/siteRootAdmin/pwd

有了這個後就可以遠端用mongodb cli或是相關的工具連上這台mongodb了

心得: fleet在這邊的作用感覺並不是很大, 同樣的東西應該也可以用chef或ansible來佈署, 這邊沒太多異質性的服務需要部屬(也只有一個服務), 而且我常會有連不上的狀況, 不過它算是蠻簡單的, 下次應該來試試kubernetes或是docker swarm

既然講到抓圖神器, 就先幫友人Wisely宣傳一下: https://play.google.com/store/apps/details?id=com.wisely.imagedownloader

不過在這並不是要宣傳抓圖神器的, 之前在找題目練習Swift時, 就想到要寫一個解析HTML的工具, 然後可以像是jsoup, 或是goquery一樣方便, 至少用來解析網頁內的連結或圖片連結方便的, 比如說像是下面的code:

let $ = SwSelect(html)
let imgUrls = $(”img”).attrs(”src”)
for url in imgUrls {
    print(url)
}

這個例子就這幾行就可以把網頁裡面圖片的Url給抓出來了, 又或, 如果我們只想抓某特定class的tag內包含的圖片(如:”<div class=“a1“><img src=“aa.jpg”/></div><div class=“a2″><img src=“a2.jpg”/></div>” 我們只想要包含在a1裡面卻不想要a2裡面的就可以用:

let $ = SwSelect(html)
let imgUrls = $(”.a1″).find(”img”).attrs(”src”)

本來我是想學goquery一樣, 複刻出一套jQuery出來的, 順便複習一下jQuery, 上面的範例其實也像是jQuery的API(不得不說這API還有selector設計真的是神來一筆), 因此選擇要把goquery從golang po到swift來, 其實是蠻可行的, 因為這兩個語言相似度還蠻高的, 但做到一半發現, 要複製整個jQuery其實蠻龐大的, 而且我只需要解析HTML的部分並不需要修改HTML的部分, 因此中途急轉彎, 把寫到一半的code, 重新包裝成這個SwSelect, 剛好也足夠達到上面兩個例子想要的啦

由於原本就是想從goquery po過來,  因此包裝了libxml2, 也把goquery用的selector parser的核心 - Cascadia給移植到Swift來, 整個SwSelect可以說是基於Cascadia, 這次也順便練習了Swift 2的一些新特色, 不過在整合libxml2上面也吃了些苦頭, 這以後有機會再寫, 目前其實應該可以使用了, 說明還沒補, Cathage的支援也還沒試過, 不過一些用法可以看一下Unit test來當範例:

https://github.com/julianshen/SwSelect/blob/master/SwSelectTests/SwSelectTests.swift

這週應該會找時間補上說明

如果有需要的話歡迎拿去使用:

https://github.com/julianshen/SwSelect