本文講的是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”。



檢查一切是否正常:


  1. 保存文件。
  2. 執行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,來構建從容器。接下來的內容是:

  1. 完全控制所有的鏡像
  2. 備份Jenkins鏡像
  3. 構建從容器


下次再會!