IFTTT在開發環境中使用Docker的經驗

jopen 9年前發布 | 28K 次閱讀 Docker

IFTTT是“if this then that”的縮寫,事實上是讓你的網絡行為能夠引發連鎖反應、讓你使用更為方便,其宗旨是“Put the internet to work for you”(讓互聯網為你服務)。Docker在IFTTT中也在開發實踐,以下是Nicholas Silva的一些介紹。

IFTTT是一款新興的互聯網工具型應用,正如他們給自己的介紹“If This Then That”,讓用戶可以根據他們設計的流程設計一些小程序,例如你可以讓它幫忙監視女朋友的推ter,當Tweet內容中出現“出差”這個詞的時 候,自動在Google Calendar里面添加一個晚上的聚會晚餐,并且在非死book發表一條消息開始呼朋喚友。

 IFTTT在開發環境中使用Docker的經驗

IFTTT目前正處于從基礎架構向容器集裝體系結構轉移的過程中。我們有一大批微服務和容器將會按照這個架構進行管理。改造我們的生產環境架構之前,我們決定首先從本地開發環境開始實踐。這樣我們就可以在冒險上生產環境之前發現應用程序的的一些問題。

另外,本地開發環境已經偏離了我們現存的生產環境。我們使用Chef(一個系統集成框架,為整個架構提供配置管理功能)和Vagrant(一款用 來構建虛擬開發環境的工具)提供管理本地虛擬機。它雖然一直在工作,但是我們知道它工作不了太久。我們需要采取行動,而不是浪費時間把測試環境同步到一個 即將棄用的生產環境中。于是我們決定直接完全躍過現存系統,直接開發我們想要的。

下面將要介紹的是我們的工程師如何在開發環境中運行使用Docker的。

目前IFTTT的工程師都使用蘋果電腦開發,所以這里的系統都是Mac OS,由于不考慮跨平臺所以不會太復雜。

我們在Dash項目中開源了所有收集的代碼。這些代碼相當難以理解,如果你盲目的運行必定會浪費很多時間。運行之前我們先來看下它都是做什么的。

Part1:啟動項目

我們使用Homebrew和Ansible運行curlbash自動化操作整個過程:

bash <(curl -fsSL https://raw.githubusercontent.com/IFTTT/dash/master/bin/bootstrap)

引導腳本安裝Homebrew和Ansible成功后下載Dash的代碼庫,使用Ansible安裝并配置VirtualBox、Docker、 Docker Machine、Docker Compose以及開發環境的DNS,然后編譯Docker Machine VM。

Ansible通常被用來管理遠程服務器,但也可以用來配置本地機器。通過參數目錄中的IP host參數127.0.0.1,你可以使用Ansible palybook執行一個本地任務。

ansible-playbook /usr/local/dev-env/ansible/mac.yml -i 127.0.0.1, --ask-become-pass

IP地址之后必須有一個逗號,因為它使參數顯示為列表,而不是一個文件的名稱。

Ansible playbook只是一個YAML文件,它列舉了一些正在運行的任務和狀態。我不會深入講這個文件,但是如果你有興趣可以看整個文件。

通過Ansible,我們安裝了提供Homebrew不包含二進制文件Caskroom,以及大量的packages和配置文件。
包括:

  • VirtualBox
  • Docker
  • Docker Machine
  • Docker Compose
  • DNS .dev resolution to the VM
  • NFS exports
  • Shell Environment
  • </ul>
    這里比較有意思的是DNS解決方案。在/etc/resolver/dev創建文件:

    nameserver 192.168.99.100

    所有的.dev請求被路由到Docker Machine中。運行一個簡單的dnsmasq容器,路由所有的.dev請求回到VM,另一個nginx代理容器路由請求到合適的容器內(待續)。不要 去修改/etc/hosts文件!來自虛擬機和你本機系統的Host類似ifttt.dev的域名時,請求可以被路由到合適的服務器。

    Part2:創建Docker Machine

    在dev命令中,我已經把幾個復雜的命令通過別名的方式整合為一個簡單文件命令。例如創建一個dev machine我們使用:

    docker-machine create \
    --driver virtualbox 
    docker-machine scp \
    /usr/local/dev-env/docker/bootsync.sh \
    dev:/tmp/bootsync.sh
    docker-machine ssh dev \
    "sudo mv /tmp/bootsync.sh /var/lib/boot2docker/bootsync.sh" docker-machine restart dev

    這條命令意思顯而易見。使用NFS和dev DNS,我們要拷貝下面這個腳本到VM中,然后重啟VM。

    這個腳本并不是很復雜:

    #!/bin/sh
    sudo umount /Users
    sudo /usr/local/etc/init.d/nfs-client start
    sleep 1
    sudo mount.nfs 192.168.99.1:/Users /Users -v -o \
    rw,async,noatime,rsize=32768,wsize=32768,proto=udp,udp,nfsvers=3 
    grep '\-\-dns' /var/lib/boot2docker/profile || {
    echo 'EXTRA_ARGS="$EXTRA_ARGS --dns 192.168.99.100 \
    --dns 8.8.8.8 --dns 8.8.4.4"' | sudo tee -a \
    /var/lib/boot2docker/profile
    }
    echo -e "nameserver 8.8.8.8\nnameserver 8.8.4.4" \
    | sudo tee /etc/resolv.conf 

    首先卸載掉標準的vboxfs并開啟NFS客戶端,然后掛載本機創建的共享目錄。Docker Machine的引導程序會在這個文件目錄存在的情況下被同步執行。

    我也是嘗試使用多種方式運行掛載命令,卻很少成功。最終成功搞定后,我就成為NFS專家了。

    首先去dnsmasq容器設置DNS文件其他剩余的部分,第二步是設置DNS服務器為8.8.X.X。這個同時關注dev域和訪問外網的請求。如 果不添加8.8.X.XDNS服務器,當你的Docker Machine的網絡發生變化,你的DNS服務器緩存將自動結束,而且不得不通過重啟機器才能轉換網絡。

    這時候你應該能連接到VM中正在運行的Docker進程。你可以通過正在運行的Docker Machine dev環境看到是如何連接的。也可以通過Docker ps 測試是都有正在運行安裝的Docker,可以看到:

    CONTAINER     ID    IMAGE     COMMAND    CREATED

    看到這個顯示說明你已經正確安裝配置環境。

    Part3:開始在容器中開發

    在容器中開發需要一些思維上的轉換,很多時候與我們過去寫代碼、運行代碼、測試等不太一樣。你有一個或者多個本地數據庫,或者可能模擬S3和DynamoDB的依賴。

    在使用容器開發之前的本地開發,你可能直接就可以關閉本機的OS或者虛擬機,或者你可以安裝一切你所需要的軟件,隨著時間的發展,你可以不斷增加 對系統和程序的配置。也可能是一個Snowflake Server。可能在管理依賴的開始會存在一些問題,因此像Bundler、pip、virtualenv和RVM他們可以統一混合使用幫助解決問題。盡 管你可以測試出這是一個新版的MYSQL,但是真正做起來就不是那么簡單了。

    在容器的范疇中,你沒必要擁有一個持久并不斷完善的來發環境。你可以做你想做的,但是這并不是推薦的工作流方式。傳統上擁有一個能在上面運行代碼的“VM”,被取而代之的是創建一個更輕量級可視化的叫做“容器”的層。( 了解更多,從Docker獲取更多說明

    這些容器都是從鏡像創建的。鏡像的本質是一些基礎的只讀模板,通過模板可以創建你的應用程序開發環境,如果移動容器,容器內所有相關的東西都將被 移動。你也可以總是通過重啟相同鏡像中的容器,當然這也是經常做的。這也就意味著你可以擁有不同的容器做不同的配置,而且完全不用擔心管理混亂。你可以一 個上面使用的是Ruby2的代碼,另一個是Ruby1.9的代碼。創建容器(基于以前使用的的Ruby鏡像)僅僅使用一個新的gem即可。當你安裝gem 處理容器時,處于其他原因,Rails2需要依賴安裝其他很多gem,你不得不考慮把gem安裝在你的系統中。

    為了獲得更多“官方”的應用程序依賴(node、mysql-client等),當運行一個存在所有應用程序的容器時,你可以用Dockerfile檢測你的應用程序庫去創建一個已經包含這些程序的鏡像。

    存在很多依賴的情況下,我們通常分離進程,分割到額外的容器中。例如我們其中一種容器,同時依賴Mysql、redis和S3。我們就把他們同時組合進入我們的容器。通過項目根目錄中的YAML文件整合進入我們的容器中。例如:

    web:
    build: .
    dockerfile: Dockerfile.development
    volumes:
    - .:/app
    links:
    - redis:redis.myapp.dev
    - s3:s3.myapp.dev
    - mysql:mysql.myapp.dev
    command: puma -C config/puma.development.rb
    mysql:
    image: mysql/mysql-server:5.6
    volumes:
    - /mnt/sda1/var/lib/mysql:/var/lib/mysql
    redis:
    image: redis:2.6
    s3:
    image: bbcnews/fake-s3

    通過此設置,很容易就可以看到依賴關聯關系。我們已經從這個目錄和Dockerfile .development文件構建web services。把這個文件作為容器的存儲部分掛上去,給這個目錄的代碼一定的執行權限就可以運行代碼。被分離的Dockerfile是最主要的,因為 在生產中我們根據此文件編譯所有的代碼以快照的形式進入容器,所以在開發環境如果我們想更改它,就必須重新編譯一次才能實現。我們覆蓋了已經在 Dockerfile中被定義的命令,因此我們能加載開發環境的配置。

    當我們開始運行web的容器時,Docker生成會意識到他關聯了redis、mysql、s3等容器,因此會自動啟動他們。除此,Docker 還將dev域名和正確的容器寫入/etc/hosts文件。由于這樣操作的話其他的人不需要什么配置,這樣就容易很多了。我們可以使用特定版本的 redis和mysql。Mysq裝載在虛擬機的一個目錄這樣使數據可以在擴容器中做到持久化保存。

    每一個項目都有一個自定義的application-level配置。這個配置文件需要運行在Dockerfile,其他的服務運行在docker-compose.yum文件。例如下面開始一個新項目,在Dash中使用dev命令:

    git clone [GIT_URL]
    dev up# (simply an alias to docker-compose up -d)

    Part 4: Web瀏覽器&服務間通信

    你可能會想,怎么才能看到編譯的結果?這又不是本地環境。完全正確,我還沒有講到怎么請求訪問到你的容器。

    如果你以前一直在操作系統中開發而不是通過虛擬機,你可能經常使用 http://localhost:8000訪問你的APP。在Docker和Docker Machine中,現在是兩個完全不一樣的抽象概念。

    然后在發生交換時,增加了分離的概念。容器在孤立我們每一個服務的同時,我們也可以更好的了解什么是真正的服務。

    為了達到和本地環境一樣的簡單級別,我們還必須做點什么才行。

    就像我得Part1中所說的一樣,一個小的dns服務器環境,不包括dnsmasq,Dev TLD的所有的請求都被路由到Docker Machine VM。我們只需要怎么把這些請求路由到正確的容器即可。不幸的是,默認的容器網絡接口并沒有暴露,VM內部容器的IP也是隨機分配。你可以綁定主機的端口 到容器的端口之上,但是必須仔細處理這之間的沖突。

    這就是Jason Wilder的Nginx反轉代理容器所講的內容。它利用Docker內部的原理關注容器的開關,然后通過nginx反轉代理實現動態配置。它也是通過綁 定了VM的80端口實現。一個新的容器都包含VIRTUAL_HOST環境變量,可以將大量的流量路由到容器中。由于能都實時運行,我們就很容易添加兩行 代碼進入Docker-compose.yml中:

    web:
    ...
    environment:
    - VIRTUAL_HOST=myapp.dev # For nginx proxy

    停止容器(停止dev中的web),刪除容器(刪除dev中的web),然后啟動所有的備份(dev環境)。這是另一個集中化環境需要轉換思維的例子。首先我停止服務,然后更改新的環境變量,重新開啟。因為新的環境變量加入新建的容器,最好的辦法是刪除容器,并重新開始。

    反轉代理也解決了對服務開發的問題:怎么定義兩個我正在開發的服務之間的依賴關系?

    把兩個不同的服務定義在兩個不同容器的docker-compose.yml中可以讓他變得更復雜。他們每次構建一個其他容器時,會循環依賴引用 另一個容器,從而進入噩夢。然而使用我們的dnsmasq容器,每一個dev的請求都被路由到nginx。只要你的服務在dev TLD而且注冊在虛擬機下,那么每一個請求都能請求所有的鏈接。我們有自己的開發者入口,可以設置為ifttt.dev開發身份,這樣就可以請求訪問 ifttt.dev內部的程序。如果你訪問的程序不在運行,nginx會返回503。

    Part5: 使用包管理

    對生產代碼來說,Dockerfile一步一步安裝相關聯的程序包是很有意義的。對于Ruby項目來說,我們使用的是Bundler做的。我們創建鏡像時可以運行bundler安裝,但是你必須保證包存在情況下才可以運行成功。沒必要但是整個Bundler打包的過程。

    然而在開發環境不同,如果我們每次都安裝一個完整的Bundler添加一個自己的gem,那將變得很脆弱。更糟糕的是如果你不包含在Dockerfile內,就不得不每次都重新啟動一個新的容器!幸運的是,我們有更好的辦法解決了這個問題:

    web:
    ...
    volumes_from:
    - bundler-cache
    ...
    bundler-cache:
    image: ruby:2.2
    command: /bin/true
    volumes:
    - /usr/local/bundle

    通過創建另一個以Ruby為基礎的鏡像服務作為我們的應用程序(使用Dockerfile定義),我們可以利用一些Docker內部更深的東西。 bundler-cache容器定義了一個使用gem安裝到系統路徑的存儲容量,一運行即可。即使容器沒有激活,我們也可以從bundler-cache 容器掛載在存儲上使用。如果刪除了這個web容器,我們依舊可以保持這個bundler-cache容器,等待下次重新創建容器時,直接掛載這個存貯容量 后所有的gem就存在了。如果你想清除緩存并重啟,很容易在開發直接刪除bundler-cache即可。

    對每一個項目都使用包管理的模式,我們發現這是一個非常方便快捷安裝管理的方法。不過最大的問題是如果你以外地刪除bundler-cache,你就必須重新安裝所有的gem。

    總結

    容器化與Docker在基礎設施層是一個非常不錯的工具。如果你計劃以后轉移到容器上,我非常推薦你在本地開發環境首先嘗試。自從 內部開始部署Dash,我們看到新開發員工的管理時間從幾天到幾小時不等。我們能夠在一周之內做到遷移(包括實際更改Dash本身),我們的工程師們已經 開始對此做出自己的貢獻。

    原文鏈接:Developing with Docker at IFTTT翻譯 :ylzhang 審校:魏小紅)

    來自:http://dockone.io/article/743

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!