本文講的是Docker在英雄聯盟遊戲中的實踐探索(四), 【編者的話】這篇博客是Riot的Docker實踐系列博客的第四篇,主要討論瞭如何添加一個基於Nginx的代理容器,以及如何用Compose來管理多容器應用。
背景
開始,瞭解我們是如何完成英雄聯盟的持續發佈,以及我們是如何發現這個技術棧可以很好地解決我們的問題。
在我們的
第一篇文章
中,我們介紹瞭如何把Jenkins放在Docker容器中。 第二篇文章
中,我們介紹瞭如何使用Docker數據卷容器來創建持久化層。我們創建了一個容器,來保存Jenkins相關的文件,以持久化插件、任務和其他的Jenkins核心數據。我們討論了數據卷容器和宿主機掛載卷之間的區別。最後,為了不持久化Jenkins war文件,我們介紹瞭如何從Jenkins主目錄中移除war文件。
在第二篇文章的末尾,我們已經有了一個功能完備的、可以保存數據的Jenkins鏡像。然而,由於若干原因,它還不完美。本篇文章將解決其中一個問題:在Jenkins之前缺少一個web代理。同時,我們將運行3個容器來構建Jenkins環境。本文將分為兩個部分:一是如何添加一個代理容器,二是如何使用Compose(一種方便的Docker工具)來管理多容器應用。
讀完本文,你將會完成一個全棧(full stack)的Jenkins主服務器。
第一部分: 代理容器
在Riot,我們使用Nginx作為代理,因為它可以容易地重定向至HTTPS,並使Jenkins監聽在8080端口,web服務器監聽在80端口。這裏不會介紹如何配置Nginx的SSL和HTTPS(互聯網上可以找到文檔和實例);相反,我將介紹如何在容器中運行Nginx代理服務器,並代理Jenkins服務器。
這一部分將涉及以下幾點:
- 創建簡單的Nginx容器
- 學習如何從本地目錄中添加文件到鏡像中,比如Nginx配置文件
- 使用Docker容器鏈接(link),連接Nginx和Jenkins
- 配置Nginx來代理Jenkins
更換OS
在Riot,我們並不常用Debian;然而,Cloudbees的Jenkins鏡像使用Debian作為默認OS,繼承自Java 8鏡像。但是,Docker的其中一個強大之處在於,我們可以使用任意的OS,因為宿主機並不在乎。這也展示了容器的“混合模式”。這意味着,如果應用在多個容器之間運行,它們並不需要是同一個OS。如果某個特定的進程需要使用某個特定的Linux發行版的庫或模塊,這種做法就很有價值了。至於應用在Debian/Centos/Ubuntu上的擴展性(spread)是否是個好主意,你們可以自行判斷。
你們可以將鏡像轉換成Ubuntu、Debian,或者任意一個OS。我們將使用CentOS 7。在本文的第四部分,我將會具體地討論如何更換Jenkins鏡像中的默認OS,並移除對於外部鏡像的依賴性。需要考慮的是,如果你更換了OS,那麼需要更改很多命令和配置,來確保Nginx正常工作。
創建Nginx Dockerfile
在你的項目根目錄中,創建一個新目錄 jenkins-nginx
,來保存另一個Dockerfile。現在,你應該有三個目錄了(如果你讀過之前的文章):
1、設置OS基礎鏡像:
FROM centos:centos7 MAINTAINER yourname
2、使用yum安裝Nginx:
RUN yum -y update; yum clean all
RUN yum -y install http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm; yum -y makecache
RUN yum -y install nginx-1.8.0
注意我們使用的Nginx版本是1.8.0。這是一個最佳實踐:總是鎖定版本,避免鏡像的重新構建使用未經測試的版本。
3、清除不需要的默認Nginx配置:
RUN rm /etc/nginx/conf.d/default.conf
RUN rm /etc/nginx/conf.d/example_ssl.conf
4、添加配置文件:
COPY conf/jenkins.conf /etc/nginx/conf.d/jenkins.conf COPY conf/nginx.conf /etc/nginx/nginx.conf
這是我們第一次使用COPY命令。ADD命令與COPY命令十分類似。
針對我們的場景,COPY是最好的選擇。就像以上文章中推薦的,我們只是拷貝單個的文件,並不需要ADD命令提供的特性(解壓tarball,基於URL的獲取等)。我們會更新默認的nginx.conf和Jenkins的配置文件。
5、我們希望Nginx監聽80端口:
EXPOSE 80
6、啓動Nginx:
CMD ["nginx"]
保存文件,但不要構建它。因為Dockerfile中有兩個COPY命令,我們需要首先創建這些文件。否則,如果這些文件不存在,構建將會失敗。
創建Nginx配置文件
以下的nginx.conf是一個默認配置,然後再修改特定的配置。
daemon off;
user nginx;
worker_processes 2;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
accept_mutex off;
}
http {
include /etc/nginx/mime.types;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
client_max_body_size 300m;
client_body_buffer_size 128k;
gzip on;
gzip_http_version 1.0;
gzip_comp_level 6;
gzip_min_length 0;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
include /etc/nginx/conf.d/*.conf;
}
現在來修改默認配置:
1、使Nginx不以daemon方式運行:
daemon off;
這是因為命令行中調用
nginx
,Nginx將以daemon方式運行在後台。這會返回 exit 0
,Docker會認為進程已經退出,然後停止容器。你會發現這種現象經常發生。對於Nginx來説,只要簡單地修改下配置就可以解決這個問題。
2、將Nginx的worker數目提升為2:
worker_processes 2;
這是我每次設置Nginx時必定做的事。當然,你可以選擇保持該配置為1。Nginx調優可以單獨寫一篇文章。我不能告訴你什麼是對的。粗略地説,該配置指定了多少個單獨的Nginx進程。CPU數目是一個不錯的參考值,當然,很多NGINX專家會説情況遠比這個要複雜。
3、事件調優(Event tuning):
use epoll; accept_mutex off;
打開epolling可以使用高效的連接模型。為了加速,我們關閉了accept_mutex,因為我們不在乎較低的連接請求數造成的資源浪費。
4、設置代理頭:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
除了關閉daemon模式之外,這是第二個必須的Jenkins代理配置。只有這樣,Jenkins才能正確地處理請求,否則會出現一些警告。
5、客户端大小:
client_max_body_size 300m;
client_body_buffer_size 128k;
你可能需要這些配置,也可能不需要。不可否認的是,300MB是一個很大的body大小。然而,我們的用户上傳文件到Jenkins服務器,其中一些是HPI插件,一些是真實文件。
6、打開GZIP:
gzip on;
gzip_http_version 1.0;
gzip_comp_level 6;
gzip_min_length 0;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
為了加速,我們打開了gzip壓縮。
保存文件為
conf/nginx.conf
。下一步就是為Jenkins添加特定的配置文件。
針對Jenkins的NGINX配置
就像上一章那樣,我會先提供一份完整的配置文件,然後再修改特定的配置。你會發現大多數內容可以在Jenkins的 官方文檔
找到。
server {
listen 80;
server_name "";
access_log off;
location / {
proxy_pass http://jenkins-master:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_max_temp_file_size 0;
proxy_connect_timeout 150;
proxy_send_timeout 100;
proxy_read_timeout 100;
proxy_buffer_size 8k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
}
只有一個配置,是真正關於代理的:
proxy_pass http://jenkins-master:8080;
這個配置需要域名
jenkins-master
存在,這個可以通過容器連接來保證(稍後會講到)。如果你還沒使用容器連接的話,那麼需要將映射Jenkins容器的IP/hostname。
然而,你不能把它設置為
localhost
。這是因為每個Docker容器都有自己的 localhost
,將代理指向了Nginx容器本身,這裏並沒有運行在8080端口的Jenkins。為了避免使用容器連接,需要執行Docker Host(應該是你的Desktop或laptop)的IP地址。儘管你是知道這個信息的,但是請想象一下,如果你的Jenkins容器是運行在Dockerhost集羣中的任意一台。你需要寫一個自動腳本,來獲取IP地址,然後編輯配置文件。這是可以做到的,但是非常麻煩。容器連接可以簡化這一過程。
構建Nginx鏡像,並連接到Jenkins鏡像
現在,我們已經創建了Nginx和Jenkins的配置文件。請確保你是在頂層目錄中。
docker build -t myjenkinsnginx jenkins-nginx/.
構建完成之後,我們可以啓動它,並連接到jenkins-master鏡像,使代理髮生作用。首先,請確保jenkins-data和jenkins-master正在運行。
docker run --name=jenkins-data myjenkinsdata
如果發生了錯誤,不用緊張,這説明容器已經存在了。這是一個好事,因為這意味着我們沒有覆蓋原來的數據。
docker stop jenkins-master
docker rm jenkins-master
docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins
現在,我們終於可以啓動Nginx容器,並連接jenkins-master:
docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
請注意
--link
參數。你可以在 Docker官方網站
上找到相關文檔。需要確保域名"jenkins-master"在NGINX容器中存在,指向jenkins-master容器的內部Docker網絡IP。
請注意Nginx容器必須在jenkins-master容器之後啓動。這意味着,如果要停止和重啓jenkins-master容器,那麼也需要重啓Nginx容器。
測試一切是否正常很容易。只要在瀏覽器中輸入IP地址,一切應該正常工作了。
如果出了問題,那麼一定有什麼東西阻塞了80端口(這更可能發生在OSX上)。請確保防火牆已經關閉,或者至少可以接受80端口的流量。如果由於某種原因,你不能清除80端口,請關閉並刪除jenkins-nginx容器,以
-p 8000:80
參數重啓它。然後,訪問 http://yourdockermachineip:8000
,看看一切是否正常。
Jenkins鏡像清理
我們已經讓Nginx監聽在80端口了,就不需要Jenkins鏡像暴露8080端口了。現在我們需要刪除這個端口配置。停止並重啓Jenkins容器,同時Nginx容器也需要重啓,因為兩者是連接在一起的。每次重啓時,它們都會連接在一起。
docker stop jenkins-nginx
docker stop jenkins-master
docker rm jenkins-nginx
docker rm jenkins-master
docker run -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins
docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
刷新瀏覽器
http://yourdockermachineiphere
。
已經不能訪問8080端口了,相反,可以通過Nginx代理訪問它了。
與往常一樣,代碼和示例都可以在GitHub上找到,地址是
https://github.com/maxfields20 ... al_04
。你會注意到makefile更新了,添加了Nginx容器和Jenkins容器的啓動順序。
Docker Compose和Jenkins
我們現在運行了3個容器,一個是Nginx代理容器,一個是Jenkins應用容器和一個保存Jenkins數據的數據卷容器。我們已經發現,因為數據卷和容器連接,這3個容器之間有啓動順序和依賴。本文將介紹Compose來處理這些。
本小節將涉及:
- 使用Compose來管理多容器應用
什麼是Compose
Compose是從另一個工具Fig發展而來的。Docker將它定義為“運行復雜應用的工具”。你可以從 https://docs.docker.com/compose/
查看相關文檔。當運行應用時,Compose可以幫我們構建鏡像,決定停止和啓動哪些容器。
例如,如果我希望運行3個容器應用,重新構建Jenkins容器,重新運行應用-可能是升級Jenkins版本。請運行以下命令:
docker stop jenkins-nginx
docker stop jenkins-master
docker rm jenkins-nginx
docker rm jenkins-master
docker build -t myjenkins jenkins-master/.
docker run --name=jenkins-master --volumes-from=jenkins-data -d myjenkins
docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
正確配置之後,我們可以運行Compose:
docker-compose stop docker-compose build docker-compose up -d
這和makefile的行為很類似。使用Compose的取捨在於,你必須額外再維護一個配置文件。
本小節單獨成章,是因為使用Docker-Compose是一個個人選擇。然而,如果你有很強的Windows開發背景,那麼Compose可能不是一個很好的選擇。
需求
- 如果你在OSX上使用Docker Toolbox, Compose是默認安裝的。
- 如果你還沒安裝Docker Toolbox,或者使用Linux,那麼請參考https://docs.docker.com/compose/install/安裝Compose
- OSX 或者 Linux
請注意,Compose還不能在Windows上與Windows版本的Docker客户端一起運行。如果你使用的是Windows和Docker Toolbox,情況會有所不同。我的建議是暫時使用makefile。Compose的開發團隊正在開發Windows兼容版本,但是1.4版本尚不支持。
第一步:創建Compos配置文件
Compose使用YAML配置文件。我們為每一個需要Compose管理的鏡像添加一個條目。
- 在你的項目根目錄中,創建一個文件docker-compose.yml
你完全可以使用另外一個名字,但是默認情況下,Compose將首先查找這個名字。
第二步: Jenkins數據容器
編輯docker-compose.yml,添加以下內容(由於它是yaml,所以需要保持縮進):
jenkinsdata: build: jenkins-data
以上是創建了一個容器的條目,叫做“jenkinsdata”。Compose不支持名字中的特殊字符,如“-”。然後,我們添加了一個構建目錄,名字是Dockerfile所在的目錄名,如“jenkins-data”。
檢查一切是否正常:
- 保存文件。
- 執行
docker-compose build。
Docker-Compose會找到jenkins-data目錄,構建Dockerfile,就像執行了
docker build jenkins-data/
一樣。你會注意到,鏡像的名字是不同的。Compose使用的命名轉換是“projectname_composecontainername”。默認情況下,項目名稱是父目錄的名字。
這種命名標準是相當重要的。這是產品環境中的容器命名方式。請確保父目錄的名字是合理的,或者使用
-p
來制定鏡像名稱。你也可以使用 -p
來區別產品環境和開發環境。
第三步: Jenkins主鏡像
繼續編輯docker-compose.xml,添加以下內容:
jenkinsmaster: build: jenkins-master volumes_from: - jenkinsdata ports: - “50000:50000”
就像Jenkins數據鏡像一樣,我們也有一個條目,來命名容器,並定義構建目錄。我們也加了一條
volumes_from
語句,與命令行中的 --volumes-from=
的作用相同。但是,請注意Compose使用的容器名並不是真正的名字。這是Compose的一個方便的特性,可以讓我們引用這些名字,提高可讀性。Compose足夠聰明,可以把它們組合在一起,來構建容器。
另一個優勢是Compose知道jenkinsmaster依賴於jenkinsdata,因此會以正確的順序來啓動它們。你可以在Compose文件中以任意順序列舉它們。
最後,我們使用
ports
指令,來處理端口映射。為了JNLP從連接,我們需要確保Jenkins主容器做50000端口映射。
第四步:Nginx鏡像
jenkinsnginx:
build: jenkins-nginx
ports:
- "80:80"
links:
- jenkinsmaster:jenkins-master
將像其他兩個條目,它也有一個名字(jenkinsnginx)和一個構建目錄。但是,我們添加了一條
links
指令,就像命令行中的 --link
。
第五步:將所有這些組合在一些
完整的docker-compose.yml:
jenkinsdata:
build: jenkins-data
jenkinsmaster:
build: jenkins-master
volumes_from:
- jenkinsdata
ports:
- "50000:50000"
jenkinsnginx:
build: jenkins-nginx
ports:
- "80:80"
links:
- jenkinsmaster:jenkins-master
我們需要構建所有這些。首先,需要確保沒有之前的容器的痕跡。如果你已經清理了,你可以跳過這一步:
docker stop jenkins-nginx
docker rm jenkins-nginx
docker stop jenkins-master
docker rm jenkins-master
docker rm jenkins-data
注意:遷移到新模型,我們只能丟掉數據容器,這很討厭。在未來的文章中,我將會討論如何備份數據。但是如果你需要備份這些數據的話,你可以先用
第三篇
的 docker up
來備份數據。
docker-compose build docker-compose up -d
注意
-d
使得Docker-Compose以daemon方式運行容器,就像Docker的參數 -d
一樣。如果你想知道哪些容器正在運行,Docker-Compose也有相類似的特性:
docker-compose ps
第六步:使用Compose維護
Compose足夠聰明到了解數據卷,並持久化。
- 在Jenkins實例中,創建一條測試任務
docker-compose stop - 簡單地編輯一下Jenkins主Dockerfile,例如更改MAINTAINER。
docker-compose build docker-compose up -d - 回到Jenkins實例,查看測試任務是否已經存在。
Docker Compose會啓動數據容器,並重新創建Nginx容器和主容器。
Compose有一個簡單的方式來清理所有的東西:
docker-compose rm
這條命令也會刪除你的數據容器。如果你不願意刪除數據容器的話,也很簡單。
docker-compose rm jenkinsmaster jenkinsgninx
總結
你可以在 https://github.com/maxfields20 ... al_05
上找到代碼和實例。
我們瞭解到Compose可以簡化多鏡像應用的管理,只需要多加一個配置文件。這個文件用來自描述容器之間的關係。
Compose是一個不錯的、可操作的工具。可能的一個缺點是,容器名是基於父目錄而定的,總是需要你指定一個項目名稱。Compose可以使用PS和RM等工具。
我們也瞭解到Compose還不能在Windows上運行。你是否使用Compose取決於你是否需要Windows支持,或者你是否喜歡Compose的命名方式。我個人很喜歡docker-compose.yml的自描述方式。你會注意到我仍然提供了makefile,因為我不需要記住所有容器的名字。
至此,基礎教程結束了。之後的文章將涉及更高級的內容。
下一步
我們還有三個高級話題:備份,構建從節點和Docker鏡像的完全控制。我將會討論如何完全製作你自己的Jenkins鏡像,而不需要依賴公共倉庫。主要是因為依賴管理,或者是因為你不喜歡基於Debian的容器,更喜歡Ubuntu或者CentOS。因此,之後我們會從頭創建自己的Dockerfiles,來構建從容器。接下來的內容是:
- 完全控制所有的鏡像
- 備份Jenkins鏡像
- 構建從容器
下次再會!