基于Nginx、Node.js和Redis的Docker工作流
本文是一篇實踐性很強的文章。作者通過一個完整的示例講述了構建一個基于Nginx、Node.js、Redis的應用服務的Docker流程。推薦所有Docker使用者閱讀,并根據文章實踐。
在我的前一篇 文章中,我已經介紹了關于容器和Docker是如何影響PaaS、微服務和云計算的。如果你剛剛接觸Docker和容器,我強烈建議你先讀一讀我之前的 文章。作為之前文章的一個延續,在本文中我仍會講述一些Docker工作流實例的內容。你可以在 GitHub上找到所有的代碼示例。
在這個例子中,我有一個非常簡單的Node.js應用,它實現了一個遞增的計數器并且將數據存儲在Redis上。為了保證應用的高可擴展的能力,我會獨立運行Redis和Node應用。
讓我們先談談容器,特別是Docker容器。我們要為每個服務/進程分配一個容器!
- 1個Redis容器
- 3個Node容器
- 1個Nginx容器 </ul>
因此,整體架構看上去是這樣的:

我可以用Docker命令來構建容器,但為了更加簡單,我推薦使用Dockerfile。我也用Docker Compose去編排應用連接容器。
首先,我先介紹下如何定義容器。
-----------------華麗的分割線--------------------
修改日期: 2015年4月3日
有多種方法來配置一個Dockerfile和鏡像層次。一個方法,將啟動一個基于操作系統的鏡像,如Ubuntu,并建立自己的應用和在這之上的依賴項。另一個可能是最理想的方法是為你的具體使用而使用一個預建的鏡像。 Docker Hub Registry有許多用于構建流行應用和其依賴的預建鏡像,這些可以直接用。
我會修改例子來演示不同的使用情況。我將演示為Redis容器使用一個預建鏡像,為Nginx容器使用一個預建的自定義配置的鏡像和一個構建在Ubuntu鏡像上的Node容器。
———————————————————————————————————————————
Redis 容器
讓我們使用官方的 Redis鏡像從Docker Hub上的Redis容器。它預打包了Redis服務的安裝,運行在默認端口6379。所以你只要默認配置ok就不需要修改任何配置,直接創建并運行Redis容器鏡像:docker run -d --name redis -p 6379:6379 redis
如果你想從基于Ubuntu的鏡像構建Redis鏡像,Dockerfile會是這樣:
# Set the base image to Ubuntu FROM ubuntuFile Author / Maintainer
MAINTAINER Anand Mani Sankar
Update the repository and install Redis Server
RUN apt-get update && apt-get install -y redis-server
Expose Redis port 6379
EXPOSE 6379
Run Redis Server
ENTRYPOINT [“/usr/bin/redis-server"]</pre>
Node 容器
讓我們來看看Node應用。我不想做太多的解釋。我做的是在每個請求使用Redis的INCR的遞增的一個視圖計數器。我使用 node-redis模塊連同 hiredis從而獲得更好的性能。(Yeah,超高性能的視圖計數器不會受損!)
var express = require('express'), http = require('http'), redis = require('redis');var app = express();
console.log(process.env.REDIS_PORT_6379_TCP_ADDR + ':' + process.env.REDIS_PORT_6379_TCP_PORT);
// APPROACH 1: Using environment variables created by Docker // var client = redis.createClient( // process.env.REDIS_PORT_6379_TCP_PORT, // process.env.REDIS_PORT_6379_TCP_ADDR // );
// APPROACH 2: Using host entries created by Docker in /etc/hosts (RECOMMENDED) var client = redis.createClient('6379', 'redis');
app.get('/', function(req, res, next) { client.incr('counter', function(err, counter) { if(err) return next(err); res.send('This page has been viewed ' + counter + ' times!'); }); });
http.createServer(app).listen(process.env.PORT || 8080, function() { console.log('Listening on port ' + (process.env.PORT || 8080)); });</pre>
你可能已經注意到用于地址和端口的Redis服務的環境變量,這些環境變量是在容器連接時由Docker定義,以方便容器間通訊。
-----------------華麗的分割線--------------------
修改日期: 2015年3月31日
Docker,除了創建環境變量,還會更新/etc/hosts文件中的主機記錄。事實上,Docker官方推薦使用/etc/hosts文件來替代環境變量,因為如果源容器重啟的時候,環境變量并不會自動更新。
———————————————————————————————————————————
接下來將我們使用另一種方法來構建Node容器。我們從一個基本的Ubuntu鏡像開始,并逐步在上面添加Node和它的依賴項。
Node Dockerfile:# Set the base image to Ubuntu FROM ubuntuFile Author / Maintainer
MAINTAINER Anand Mani Sankar
Update the repository
RUN apt-get update
Install Node.js and other dependencies
RUN apt-get -y install curl RUN curl -sL https://deb.nodesource.com/setup | sudo bash - RUN apt-get -y install python build-essential nodejs
Install nodemon
RUN npm install -g nodemon
Bundle app source
COPY . /src
Install app dependencies
RUN cd /src; npm install
Expose port
EXPOSE 8080
Run app using nodemon
CMD ["nodemon", “/src/index.js"]</pre>
上面的Dockerfile解釋如下:
- 從Docker Hub拉取Ubuntu基礎鏡像
- 使用apt-get安裝Node.js以及依賴
- 使用npm安裝nodemon
- 從host目錄復制應用源碼到容器內src
- 運行npm install安裝Node應用依賴
- 端口8080從容器拋出,使用nodemon運行應用 </ul>
使用Dockerfile構建一個Docker鏡像:
docker build -t msanand/node .
從自定義鏡像中創建一個Node容器并連接Redis容器:
docker run -d --name node -p 8080 --link redis:redis msanand/node
由于我計劃在3個node服務器之間做負載均衡,我會創建3個容器 - node1、node2和node3。
請注意,Redis容器將會連接到Node容器,所以Node容器可以通過Docker創建的主機記錄或者環境變量定義的IP地址和端口來與Redis容器交互。
有了這一點,我有一個Node應用顯示一個視圖計數器并將數據保存在Redis。讓我們來看看如何使用Nginx來做負載均衡。
NGINX容器
Nginx的核心是它的配置:一個conf文件。我使用一個簡單的Nginx配置文件定義3個upstream server:worker_processes 4;events { worker_connections 1024; }
http {
upstream node-app { least_conn; server node1:8080 weight=10 max_fails=3 fail_timeout=30s; server node2:8080 weight=10 max_fails=3 fail_timeout=30s; server node3:8080 weight=10 max_fails=3 fail_timeout=30s; }
server { listen 80;
location / { proxy_pass http://node-app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } }</pre>
我已經注冊一個node-appupstream server,它會負載均衡3個服務:node1、node2、node3。我還通過一個全等加權least_conn負載平衡策略指定了每個服務器上的活動連接數的負載均衡。或者,你可以使用一個基于負載均衡方法的羅賓循環( round robin)或IP哈希或鍵哈希。Nginx監聽80端口,它基于負載均衡策略代理請求到上游服務器node-app。如果要了解更多的Nginx的配置我會另外討論。
為了構建Nginx容器,我計劃從Docker Hub上使用正式的 Nignx鏡像。我將使用一個Dockerfile以及我自定義的Nginx conf文件配置Nignx。
該Dockerfile是最小的-使用Nginx鏡像和副本自定義Nginx的配置:# Set nginx base image FROM nginxFile Author / Maintainer
MAINTAINER Anand Mani Sankar
Copy custom configuration file from the current directory
COPY nginx.conf /etc/nginx/nginx.conf</pre>
構建Docker鏡像:
docker build -t msanand/nginx .
從鏡像中創建一個Nginx容器,并連接到Node容器:
docker run -d --name nginx -p 80:80 --link node:node msanand/nginx
最后,我們有個Nginx服務器負載均衡3個Node服務器。回過來談Node服務器,他們每一個運行在自己的容器中!
如果我們要創建一個基本的Ubuntu鏡像定制Nginx的鏡像,Dockerfile會是這個樣子:# Set the base image to Ubuntu FROM ubuntuFile Author / Maintainer
MAINTAINER Anand Mani Sankar
Install Nginx
Add application repository URL to the default sources
RUN echo "deb
Update the repository
RUN apt-get update
Install necessary tools
RUN apt-get install -y nano wget dialog net-tools
Download and Install Nginx
RUN apt-get install -y nginx
Remove the default Nginx configuration file
RUN rm -v /etc/nginx/nginx.conf
Copy a configuration file from the current directory
ADD nginx.conf /etc/nginx/
Append "daemon off;" to the configuration file
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
Expose ports
EXPOSE 80
Set the default command to execute when creating a new container
CMD service nginx start</pre>
Dockerfile(daemon off)配置了Nginx不以守護進程運 行,這也是必須的,因為Docker容器本身就是無狀態的,只有當他們所承載的進程運行的時候,容器才有存在的意義。所以把Nginx當成后臺進程運行根 本不可能。相反,把Nginx作為一個服務運行可以確保容器的正常運行。官方Nginx鏡像默認配置也是這樣的。Docker Compose編排應用
Compose是一個使用Docker定義和運行復雜應用的工具。
使用單獨的命令來構建鏡像并運行和連接容器非常繁瑣和復雜,特別是你要運行多個容器的時候。
Docker Compose讓你在一個文件中定義多容器應用并用一個命令使應用程序運行起來。
我已經定義一個Docker Compose YAML文件,如下:
nginx: build: ./nginx links: - node1:node1 - node2:node2 - node3:node3 ports: - "80:80" node1: build: ./node links: - redis ports: - "8080" volumes: - node:/src node2: build: ./node links: - redis ports: - "8080" volumes: - node:/src node3: build: ./node links: - redis ports: - "8080" volumes: - node:/src redis: image: redis ports: - “6379"
YAML文件定義每個容器的名字,指向一個用于構建的Dockerfile。此外,它包含所述容器連接并通過它們暴露端口。由于Redis容器使用Redis官方鏡像,所以不必構建。
只需要一個命令,Docker Compose就可以構建所需鏡像,并導出所需端口,然后通過YAML中的定義運行和連接容器。所有你需要做的就是運行docker-compose up,然后你的5個容器應用就會啟動并運行。輸入你的主機URL和80端口,你就可以看到你的視圖計數器!
Docker Compose的一個顯著特點就是具有動態擴展容器的能力。使用命令docker-compose scale node=5可 以擴展容器的數量來運行一個服務。如果你已有一個基于微服務架構的Docker,你可以輕松地擴展,并動態地根據負載分配具體服務。理想情況下,我寧愿定 義一個node服務并使用Docker Compose來擴展它。但我還沒有想出一個方法來動態地調整Nginx的配置。如果你有這方面的想法,請在本文后回復。
但這里有個需要注意的是,Docker Compose還不能用于生產環境。文檔建議使用在開發環境中,而不是生產環境。但也有其它的容器編排引擎如我之前的文章 討論的Kubernetes。
持續集成和部署
我在我的 GitHub倉庫中配置了2個hook服務(譯者注:作者指的是GitHub Webhook)。
- CircleCI-用于持續集成(以及部署)
- Docker Hub -用于Docker構建(continuous Docker builds) </ul>
CircleCI YAML配置文件看這兒:
machine: services: - dockerdependencies: override: - sudo pip install -U docker-compose
test: override: - docker-compose run -d --no-deps node1 - cd node; mocha</pre>
YAML配置文件使用CircleCI提供的Docker服務,并安裝Docker Compose依賴,創建了Node容器(未連接到Redis容器)。它使用Mocha(譯者注:Mocha是一個基于Node.js和瀏覽器的集合各種 特性的JavaScript測試框架,并且可以讓異步測試也變的簡單和有趣。Mocha的測試是連續的,在正確的測試條件中遇到未捕獲的異常時,會給出靈 活且準確的報告。Mocha托管在Github上)在Node應用上觸發測試,這確保了GitHub上每個提交都會對應一個測試。![]()
每次提交都會觸發我的 Docker Hub Repository進行一次構建。這確保在Docker Hub中通過持續部署到生產環境的最終鏡像總是可用的。生產環境能在任何時間從Docker Hub和從容器中編排的應用中能拉到最終鏡像。
以上是我的一個基于Nginx、Node.js和Redis的Docker流程實例。如果你有任何建議和更好的方法,請發表評論。
原文鏈接:A sample Docker workflow with Nginx, Node.js and Redis (翻譯:吳錦晟 校對:李穎杰)
來自:http://dockerone.com/article/291