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

在Swift裡, Protocol和Protocol extension是非常強悍的東西, 比起OO世界的Class, 其實是更加犀利, 在今年(2015)的WWDC有一場個人很推薦的Protocol-Oriented Programming in Swift , 這場講的蠻精闢的, 熱門到後來加開了第二場, 今年我去WWDC, 我自己就兩場都去了, 回來又再回為了一次, 相當建議聽看看的:

https://developer.apple.com/videos/play/wwdc2015-408/

好, 這篇重點不是推薦這場演講….而是怎在Swift中去建立Array extension? 這好像沒什麼難的, 不就像下面這樣就可以了?

extension Array {
   func concat() -> String {
       var str = “”
       for i in self {
           str += String(i)
       }
       return str
   }
}

沒錯, 這是最簡單的方式, 但其實有個問題, Array內的元件(element)是泛型, 也就可能是任意的型態, 以這個例子來說, 也有可能是無法轉成String的型態, 在這邊就有可能出問題, 因為這寫法會被套用到所有的Array型態去, 不管是String array, In array或其他, 但大部分現實的例子(也就是我手上在寫的東西剛好碰到的例子), 我們只希望延伸出的東西只被套用到某些特定的型態, 這時候就得加上”where”來解決這問題:


以上面這例子, 雖然字串陣列跟整數陣列都有find這函式, 參數型態也一樣(Self.Generator.Element), 但這兩者的find的行為是不同的, 以字串陣列來說是找含有這一字串的, 而以整數陣列來說則是找一樣的, 這邊就是靠extension裡的where

這邊有個不一樣的地方是, Array被換成CollectionType了, 這是因為where裡面要判斷Element是不是我們所要的型態, 而Array是一個struct, CollectionType才是一個Protocol, 所以這邊我們用CollectionType來達到這目的

實在有點詭異的問題, 解決後又覺得實在有點蠢, 結果這問題足足花了我一個半小時才得到答案

問題是這樣的, 我拿了這一個字串 “‘x\\r\nx’” 來做解析, 一個個Character去檢查是不是”\r”, 如:

let x=“'x\\r\nx’”
for s in x.characters {
    if s == “\r” {
         print(”got you!”)
    }
}

邏輯上看起來沒啥問題, 但執行結果一直不如預期(沒印出”got you”), 把第四個字元substring出來它也不等於字串”\r”

百思不得其解, 結果最後在\r\n中間加一個空白後, 就得到預期的結果了, 原來….它是把”\r\n”整個當成一個字元!!!