通過做一個基於Node的微伺服器來學習Docker
如果你正準備著手學習 Docker,別再觀望,動起手來吧!
在這篇文章中,我將告訴你 Docker 是如何工作的?使用中會遇到什麼問題?如何通過 Docker 完成一個基本的開發任務——構建一個微伺服器。
我們將以一臺配有 Node.js 服務和 MySQL 後臺的伺服器為例,從在本地執行程式碼開始,完成一個執行著微服務和資料庫的容器。
什麼是 Docker ?
從本質上來說,Docker 是一種軟體,讓使用者建立映象檔案(就像虛擬機器中的模板),然後在容器中執行這個映象的例項。
Docker 維護著有著大量映象的儲存庫,名字叫 Docker Hub ,你可以將它作為嘗試映象的起始點,或者用來免費儲存你的映象。你可以安裝 Docker ,選擇你喜歡的映象,然後在容器中執行它的例項。
本文我們將介紹建立映象、從映象建立容器等一系列內容。
安裝 Docker
如果你想跟上本文的節奏,那麼你需要安裝 Docker 。
如果你是 Mac 或者 Windows 作業系統,那麼你需要使用虛擬機器。我在 Mac OS X 上使用 Parallels 安裝 Ubuntu 虛擬機器來應付大多數的開發任務。因為它支援快照功能,當你做實驗的時候,他可以方便的將破壞了的環境恢復回去。
試試看
輸入以下命令:
Shell1 | docker run-it ubuntu |
一段時間後,你將會看到如下提示:
Shell1 | root@719059da250d:/# |
試試如下的命令,然後退出容器:
Shell1234567 | root@719059da250d:/# lsb_release -a No LSB modules are available.Distributor ID:Ubuntu Description:Ubuntu14.04.4LTS Release:14.04Codename:trusty root@719059da250d:/# exit |
這看起來沒什麼,但是其實在後臺發生了很多事情。
你看到的是在你的機器上執行著的 Ubuntu 的隔離容器環境裡的 bash shell。這個環境完全歸你所有——可以在上面安裝軟體,執行軟體,可以做任何你想做的事情。
下圖表明瞭剛剛發生了什麼(圖來自於《 理解 Docker 架構 》一文):
1. 列出如下的 Docker 指令:
- docker : 執行 docker 客戶端
- run : 執行一個新的容器
- -it :讓容器帶有“互動終端”的一個引數
- ubuntu : 容器所依賴的基礎映象
2. 在主機(我們的機器)上執行的 docker 服務檢查本地是否有所請求的映象拷貝——這裡發現沒有。
3. docker 服務檢查公有儲存庫(the docker hub),看是否有可用的名為 ubuntu
的映象——這裡發現有。
4. docker 服務下載映象,將其儲存到本地快取裡(為了下一次直接使用)。
5. docker 服務基於 ubuntu 映象建立新的容器。
Try any of these:
試試下面這些命令:
Shell123 | docker run-it haskell docker run-it java docker run-it python |
我們沒準備使用 Haskell ,但是你可以看到,搭建一個環境是多麼容易。
構建自己的映象也很輕鬆,可以在這上面安裝應用程式或者服務,可以是資料庫,或者是其他你需要的。隨後就可以在任意安裝了 Docker 的機器上執行它們——要保證映象是相同的、可預測的方式在每臺機器上執行。我們可以將軟體及其執行所需的環境整體構建成程式碼,並且輕鬆部署。
讓我們以一個簡單微伺服器為例。
概述
我們將要用 Node.js 和 MySQL 建立一個讓我們管理郵件地址到電話號碼目錄的微服務。
開始
要完成本地開發,需要安裝MySQL,並且建立一個測試資料庫…
…搖頭。
建立本地資料庫,並且上面執行指令碼,這很容易,但是可能會帶來一些問題。很多不受控制的事情開始了。它可能工作,我們甚至可以通過提交進程式碼庫的 shell 指令碼來控制這些步驟,但是如果其他開發人員已經安裝了 MySQL 了呢?如果他們的資料庫已經使用了我們想要建立的名稱 ‘users’ 了呢?
第一步:在 Docker 中建立一個數據庫測試伺服器
這是很好的 Docker 應用場景。我們可能不想在 Docker 裡執行生產環境資料庫(比如可能會使用 Amazon RDS),但是可以使用 Docker 容器建立一個乾淨的 MySQL 資料庫做開發——讓我們的開發及其保持乾淨,並且保證所有東西都在控制中,並且可重複使用。
執行下面的命令:
Shell1 | docker run--name db-d-eMYSQL_ROOT_PASSWORD=123-p3306:3306mysql:latest |
該命令啟動一個執行著的 MySQL 例項,通過 3306 埠訪問,root 密碼為 123 。
docker run
告訴引擎,使用者想要執行一個映象(在最後傳入的是映象,mysql:latest )- –name db 將整個容器命名為 db 。
- -d detach,在後臺執行容器。
- -e MYSQL_ROOT_PASSWORD=123(或者是 –env)環境變數 – 引數告訴 docker 所提供的環境變數。這之後跟著的變數正是 MySQL 映象檢查且用來設定的預設 root 密碼。
- -p
3306:3306(或者 --publish)
告訴引擎使用者想要將容器內的3306埠對映到外部的3306埠上。
最後一部分很重要——即使這是 MySQL 的預設埠,如果使用者不顯式告訴 docker 想要對映的埠,docker 就會阻塞該埠的訪問(因為容器預設是隔離的,直到使用者告訴 docker 想要訪問它們)。
該命令返回值是容器 id,這是容器的指標,使用者可以用它來停止容器,向容器傳送命令等等。讓我們看看正在執行的是哪些容器:
Shell123 | $docker psCONTAINER IDIMAGE...NAMES36e68b966fd0mysql:latest...db |
關鍵的資訊是容器 ID,映象和名稱。連線到這個映象看看裡面有什麼:
Shell1234567891011121314 | $docker exec-it db/bin/bashroot@36e68b966fd0:/# mysql -uroot -p123 mysql>show databases;+--------------------+|Database|+--------------------+|information_schema|+--------------------+1rows inset(0.01sec)mysql>exitBye root@36e68b966fd0:/# exit |
下面這麼做也很有意思:
1. docker exec -it db
:告訴 docker 使用者想要在名為 db
的容器裡執行一個命令(我們也可以使用 id,或者 id 的前幾個字母)。 -it
確保使用者有互動型終端。
2. mysql -uroot -p123
:我們實際在容器裡作為程序執行的命令,這裡是 mysql 客戶端。
我們可以建立資料庫,表,使用者,其他你需要的等等。
打包測試資料庫
在容器內執行 MySQL 需要一些 Docker 技巧,但是讓我們先打住,看看服務。現在,使用指令碼建立一個 test-database
目錄來啟動資料庫,停止資料庫以及搭建測試資料:
123 | test-databasesetup.sqltest-databasestart.shtest-databasestop.sh |
啟動指令碼很簡單:
Shell123456789101112131415161718 | #!/bin/sh# Run the MySQL container, with a database named 'users' and credentials# for a users-service user which can access it.echo"Starting DB..."docker run--name db-d-eMYSQL_ROOT_PASSWORD=123-eMYSQL_DATABASE=users-eMYSQL_USER=users_service-eMYSQL_PASSWORD=123-p3306:3306mysql:latest# Wait for the database service to start up.echo"Waiting for DB to start up..."docker exec db mysqladmin--silent--wait=30-uusers_service-p123 ping||exit1# Run the setup script.echo"Setting up initial data..."docker exec-idb mysql-uusers_service-p123 users<setup.sql |
該指令碼在一個分離容器鍾執行資料庫映象(比如,在後臺執行),建立了一個使用者來訪問 users
資料庫,然後等待資料庫伺服器啟動,隨後執行 setup.sql
指令碼來設定初始資料。
setup.sql 的內容是:
MySQL123456 | createtabledirectory(user_idINTNOT NULLAUTO_INCREMENTPRIMARY KEY,emailTEXT,phone_numberTEXT);insertintodirectory(email,phone_number)values('[email protected]','+1 888 123 1111');insertintodirectory(email,phone_number)values('[email protected]','+1 888 123 1112');insertintodirectory(email,phone_number)values('[email protected]','+1 888 123 1113');insertintodirectory(email,phone_number)values('[email protected]','+1 888 123 1114');insertintodirectory(email,phone_number)values('[email protected]','+1 888 123 1115'); |
stop.sh
指令碼會停止容器並且刪除容器(docker 預設會保留容器,這樣能夠快速重啟,本示例中並不需要這樣):
1234 | #!/bin/sh# Stop the db and remove the container.docker stop db&&docker rmdb |
之後會進一步簡化這個過程,讓它更加順暢。在 repo 裡的 step1 分支裡檢視這一階段的程式碼。
第二步:用 Node.js 建立一個微服務
本文的主題是 Docker 的學習,因此並不會花太多篇幅講解 Node.js 的微服務。只是強調一些重點。
Shell12345678 | test-database/# contains the code seen in Step 1 users-service/# root of our node.js microservice -package.json# dependencies, metadata-index.js# main entrypoint of the app-api/# our apis and api tests-config/# config for the app-repository/# abstraction over our db-server/# server setup code |
讓我們仔細看看這一部分。首先看看這個程式碼庫。最好將你的資料庫訪問封裝和抽象成一些類,允許模擬它來實現測試目的:
JavaScript123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 | // repository.js//// Exposes a single function - 'connect', which returns// a connected repository. Call 'disconnect' on this object when you're done.'use strict';varmysql=require('mysql');// Class which holds an open connection to a repository// and exposes some simple functions for accessing data.classRepository{constructor(connection){this.connection=connection;}getUsers(){returnnewPromise((resolve,reject)=>{this.connection.query('SELECT email, phone_number FROM directory',(err,results)=>{if(er |