Docker在英雄聯盟游戲中的實踐探索(五)
【編者的話】 這篇博客是Riot的Docker實踐系列博客的第五篇,主要討論了如何從頭創建你的Docker鏡像。
在以前的 帖子中, 我們講解了如何創建一系列Docker鏡像來部署Jenkins容器。雖然我們原本打算在生產環境中部署Jenkins服務器容器,但是目前尚未這么做。 我們的團隊從這項工作中獲得的真正價值是如何快速創建Jenkins測試環境。這些環境可以用來測試插件,或者重現我們遇到的問題。例如,使用容器可以更 容易地測試Jenkins升級對于特定配置的影響。我們可以使用數據卷來持久化數據,然后大膽地升級Jenkins主鏡像。
這些例子也教會了我自己如何配置更多復雜的Docker選項。比如,真實世界的場景中,Docker是如何工作的?如何解決持久化問題?一年前, 我很難找到好的文檔。我們以這一系列博客為基礎,來探索其他的可能性,如使用Docker容器創建從節點。這種靈活性使得我們團隊可以自行開發工具,來構 建流水線和構建環境。
一年前我們開始嘗試時,還沒有今天這么完備的Cloudbees Jenkins鏡像。根據我們是如何在生產服務器上部署Jenkins,我們手動創建了jenkins-master鏡像。這也就是這篇文章的主要內容: 拆解Cloudbees鏡像,真正了解它是如何工作的。當我們在做這個的時候,我們學到了很多關于Jenkins的知識。通過完全控制自己的 Dockerfile,可以修改和消除我們不想要的依賴。
某種程度上,管理依賴是很主觀的。對于我們來說,我們希望盡量減少公共依賴。在Docker術語中,這就是關于如何使用“FROM”語句,從哪里 獲取基礎鏡像。如果你想知道你的鏡像是從哪里獲取基礎鏡像,鏡像里有哪些內容,那么這篇文章正是你想要的。同樣的,如果你想更改默認的操作系統、Java 版本,或者刪除一些Cloudbees容器的特性,也可以參考這篇文章。如果你只是滿足于“It just works”,那么這篇文章可能并不適合你。
編寫自己的鏡像有一下這些好處:
- 控制鏡像的默認操作系統。如果Dockerfile依賴于一連串的FROM語句,哪一個最先指定了操作系統?因此,要想改變鏡像,就要先了解鏡像是如何形成的。
- 在繼承鏈中的每一個鏡像都可能來自于一個公共源,并有可能在未經警告的情況下改變,或者包含不想用的內容。這是一個安全隱患。 </ul>
發現依賴
第一步是弄清楚dockerfile中的依賴項列表是什么。到目前為止,在所有課程中,我們使用的都是公共的Jenkins CI Dockerfile。讓我們從這里開始。首先,先找到定義了我們正在使用的鏡像的Dockerfile。Dockerhub可以方便地幫助我們找到想要的Dockerfiles。為了找出我們所使用的鏡像,我們需要看看我們先前創建的Jenkins主節點的Dockerfile。
FROM jenkins:1.609.1 MAINTAINER Maxfield StewartPrep Jenkins Directories
USER root RUN mkdir /var/log/jenkins RUN mkdir /var/cache/jenkins RUN chown -R jenkins:jenkins /var/log/jenkins RUN chown -R jenkins:jenkins /var/cache/jenkins USER jenkins
Set list of plugins to download / update in plugins.txt like this
pluginID:version
credentials:1.18
maven-plugin:2.7.1
...
NOTE : Just set pluginID to download latest version of plugin.
NOTE : All plugins need to be listed as there is no transitive dependency resolution.
COPY plugins.txt /usr/share/jenkins/plugins.txt RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins.txt
Set Defaults
ENV JAVA_OPTS="-Xmx8192m" ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war"</pre>
我們可以看到,FROM語句指向了Jenkins :1.609.1。在Dockerfile術語中,這里表示名為Jenkins、標記為1.609.1的鏡像,這個標記也是Jenkins的版本號。讓我們來看一下Dockerhub。
- 訪問http://hub.docker.com。
- 如果你想在Dockerhub中分享公開鏡像,你可以注冊一個帳戶。不過,這篇教程并不需要它。
- 在搜索窗口中輸入鏡像名稱:Jenkins。
</li>- 返回了一系列的鏡像倉庫,點擊最上面的Jenkins。
</li>- 你現在可以看到這個鏡像的詳細描述。
- Jenkins鏡像提供了指向1.625.2版本的鏈接。這意味著自從我開始這些教程到現在,版本已經更新了。點擊這個鏈接。
</li>- 這個鏈接可以把我們鏈接到GitHub上,也就是我們使用的Dockerfile。
</ol>
我們的目標是復制這個Dockerfile,但自己管理依賴,因此保存下來這個Dockerfile。在本教程的結尾,我們會有一個完整的依賴項列表,并形成一個新的Dockerfile。目前的Jenkins Dockerfile是:
FROM java:8-jdk RUN apt-get update && apt-get install -y wget git curl zip && rm -rf /var/lib/apt/lists/*ENV JENKINS_HOME /var/jenkins_home ENV JENKINS_SLAVE_AGENT_PORT 50000
Jenkins is run with user
jenkins
, uid = 1000If you bind mount a volume from the host or a data container,
ensure you use the same uid
RUN useradd -d "$JENKINS_HOME" -u 1000 -m -s /bin/bash jenkins
Jenkins home directory is a volume, so configuration and build history
can be persisted and survive image upgrades
VOLUME /var/jenkins_home
/usr/share/jenkins/ref/
contains all reference configuration we wantto set on a fresh new installation. Use it to bundle additional plugins
or config file with your custom jenkins Docker image.
RUN mkdir -p /usr/share/jenkins/ref/init.groovy.d
ENV TINI_SHA 066ad710107dc7ee05d3aa6e4974f01dc98f3888
Use tini as subreaper in Docker container to adopt zombie processes
RUN curl -fL https://github.com/krallin/tini/releases/download/v0.5.0/tini-static -o /bin/tini && chmod +x /bin/tini \ && echo "$TINI_SHA /bin/tini" | sha1sum -c -
COPY init.groovy /usr/share/jenkins/ref/init.groovy.d/tcp-slave-agent-port.groovy
ENV JENKINS_VERSION 1.625.2 ENV JENKINS_SHA 395fe6975cf75d93d9fafdafe96d9aab1996233b
could use ADD but this one does not check Last-Modified header
see https://github.com/docker/docker/issues/8331
RUN curl -fL http://mirrors.jenkins-ci.org/war-stable/$JENKINS_VERSION/jenkins.war -o /usr/share/jenkins/jenkins.war \ && echo "$JENKINS_SHA /usr/share/jenkins/jenkins.war" | sha1sum -c -
ENV JENKINS_UC https://updates.jenkins-ci.org
RUN chown -R jenkins "$JENKINS_HOME" /usr/share/jenkins/ref
for main web interface:
EXPOSE 8080
will be used by attached slave agents:
EXPOSE 50000
ENV COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log
USER jenkins
COPY jenkins.sh /usr/local/bin/jenkins.sh
ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
from a derived Dockerfile, can use
RUN plugins.sh active.txt
to setup /usr/share/jenkins/ref/plugins from a support bundleCOPY plugins.sh /usr/local/bin/plugins.sh</pre>
最重要的是,Jenkins使用了FROM Java:8-jdk,接下來我們需要追蹤這個Dockerfile。
在我們開始這么做之前,我們需要了解這個文件中的一切。Cloudbees已經投入了大量工作來完成一個可靠的Docker鏡像,因此我們可以盡量利用這一點。要注意的要點如下:
- 環境變量有JENKINS_HOME, JENKINS_SLAVE_PORT, TINI_SHA, JENKINS_UC, JENKINS_VERSION和COPY_REFERNCE_FILE_LOG。
- 鏡像使用Tini來管理僵尸進程,這很有趣。因為Cloudbees覺得這是有必要的,因此,我們也會保留這一點。
- Jenkins的war文件是通過curl下載到鏡像中的。
- 使用apt-get安裝了wget、curl和git。
- 有三個文件是被復制到容器中的: jenkins.sh, plugins.sh和init.groovy。
- 暴露了一些端口: 8080和50000,前者是Jenkins的偵聽端口,后者是從節點與Jenkins的通信端口。
</ol>
這需要花大量時間和精力來維護。
我們需要追蹤每一個FROM語句,直到找到基礎鏡像中的操作系統。再次搜索Dockerhub中的下一個鏡像:Java:8-jdk。
- 在Dockerhub搜索窗口中,搜索“java”。(確保你在Dockerhub主頁,而不只是在搜索Jenkins的頁面)。
- 就像搜索Jenkins一樣,我們點擊返回結果中的第一個。
- 在“Supported tags”下面,我們可以看到Java有很多不同的標簽和鏡像。找到“8-jdk”這個標簽,點擊它的Dockerfile。
這是一個有趣的鏡像。Java 8-jdk鏡像又引用另一個公共鏡像buildpack-deps:jessie。因此,我們將要探索下一個鏡像,但是我們還沒有搞清楚這個鏡像做了什么。 </li> </ol>
這一鏡像做了以下事情:
- 安裝Unzip。
- 使用apt-get安裝opendjdk-8。
- 安裝ca_certificates。
- 創建了debian:jessie backports,可以確認該鏡像使用Debian:jessie。
</ol>
以下是完整的Dockerfile:
FROM buildpack-deps:jessie-scmA few problems with compiling Java from source:
1. Oracle. Licensing prevents us from redistributing the official JDK.
2. Compiling OpenJDK also requires the JDK to be installed, and it gets
really hairy.
RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/* RUN echo 'deb
Default to UTF-8 file.encoding
ENV LANG C.UTF-8
ENV JAVA_VERSION 8u66 ENV JAVA_DEBIAN_VERSION 8u66-b17-1~bpo8+1
see https://bugs.debian.org/775775
and https://github.com/docker-library/java/issues/19#issuecomment-70546872
ENV CA_CERTIFICATES_JAVA_VERSION 20140324
RUN set -x \ && apt-get update \ && apt-get install -y \ openjdk-8-jdk="$JAVA_DEBIAN_VERSION" \ ca-certificates-java="$CA_CERTIFICATES_JAVA_VERSION" \ && rm -rf /var/lib/apt/lists/*
see CA_CERTIFICATES_JAVA_VERSION notes above
RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure
If you're reading this and have any feedback on how this image could be
improved, please open an issue or a pull request so we can discuss it!</pre>
我們需要復制這一切。現在,讓我們去尋找下一個Dockerfile,也就是buildpack-deps:jessie-scm。我們重復之前的過程:
- 在Dockerhub主頁面上搜索“buildpack-deps”,選擇第一個結果。
- jessie-scm是"Supported Tags "的第二項。點擊Dockerfile。
</li> </ol>
這個Dockerfile很短小。 我們可以看到它依賴于buildpack-deps:jessie-curl。但除此之外,Dockerfile安裝了五個東西。
- BZR
- Git
- mercurial
- openssh-client
- subversion
</ol>
因為這是一個SCM鏡像,所以這是合理的。你需要衡量是否需要復制這些特定行為。首先,Cloudbees Jenkins鏡像已經安裝了Git。如果你不需要使用bazaar、mercurial或subversion,那么你也許不需要安裝它們,從而節省一 部分空間。為了完整起見,這里是完整的Dockerfile:
FROM buildpack-deps:jessie-curlRUN apt-get update && apt-get install -y --no-install-recommends \ bzr \ git \ mercurial \ openssh-client \ subversion \ && rm -rf /var/lib/apt/lists/*</pre>
讓我們繼續看下一個依賴。回到Dockerhub搜索頁面。
- 搜索"buildpack-deps",選擇第一個結果。
- 點擊jessie-curl。
</ol>
看到這里,我們終于找到了最后的依賴。這個鏡像有一個FROM語句:debian:jessie,這是一個操作系統。我們可以看到,這個鏡像安裝了一些應用程序:
- wget
- curl
- ca-certificates
</ol>
這很有趣,因為其他的鏡像已經安裝了這些項目。我們不需要這一鏡像,因為它沒有增加新的價值。
現在,我們已經完成了對Jenkins基礎鏡像的探索。我們找到了需要注意和復制的內容,也發現一些東西是不需要的。這是完整的依賴鏈:
不要忘記:在之前的教程中,我們是基于Jenkins鏡像來創建Dockerfile,下一步是如何創建自己的Dockerfile。
自己做DOCKERFILE
通過以上對于依賴的研究,我們現在可以從頭創建自己的Dockerfile。最簡單的方法就是剪切和粘貼所有內容,然后去掉FROM語句。這是可行的,但也會產生一些冗余指令和缺陷。我們可以通過刪除一些不需要的東西,來減少鏡像大小。
完整的鏡像鏈是構建在Debian:Jessie的基礎上,在這個教程中,我會講解如何創建這一切。最后,我會提供另一個鏈接,是以CentOS7為基礎來構建的。哪一個OS都是可以的,Docker的好處之一就是允許你選擇希望使用的操作系統。
下面,讓我們開始制作一個完全更新的jenkins-master鏡像。這是目前jenkins-mater的Dockerfile(如果你已經學習了所有的教程):
FROM jenkins:1.609.1 MAINTAINER Maxfield StewartPrep Jenkins Directories
USER root RUN mkdir /var/log/jenkins RUN mkdir /var/cache/jenkins RUN chown -R jenkins:jenkins /var/log/jenkins RUN chown -R jenkins:jenkins /var/cache/jenkins USER jenkins
Set Defaults
ENV JAVA_OPTS="-Xmx8192m" ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war"</pre>
這里值得注意的是,我們從Jenkins 1.609.1遷移到了1.625.1。基于我們在過去幾周里學到的內容,我們更新了一些JAVA_OPTS設置。
第一步:讓我們修改FROM語句,使用Debian。
- 使用你最喜歡的編輯器,打開jenkins-master/Dockerfile。
- 替換FROM語句:FROM debian:jessie。
</ol>
下一步,我們使用apt-get安裝了所有的應用程序。添加以下內容:
RUN echo 'debENV LANG C.UTF-8 ENV JAVA_VERSION 8u66 ENV JAVA_DEBIAN_VERSION 8u66-b17-1~bpo8+1
see https://bugs.debian.org/775775
and https://github.com/docker-library/java/issues/19#issuecomment-70546872
ENV CA_CERTIFICATES_JAVA_VERSION 20140324
RUN apt-get update \ && apt-get install -y --no-install-recommends \ wget \ curl \ ca-certificates \ zip \ openssh-client \ unzip \ openjdk-8-jdk="$JAVA_DEBIAN_VERSION" \ ca-certificates-java="$CA_CERTIFICATES_JAVA_VERSION" \ && rm -rf /var/lib/apt/lists/*
RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure</pre>
這里有很多內容。你會注意到我將所有Dockerfile中apt-get安裝的應用集中到了一起。為了這么做,我需要設置所有必要的關于Java版本和證書的環境變量。我建議在繼續添加內容之前,先充分地測試它。docker build jenkins-master/
我們只是測試了安裝程序是正常的,但是這個鏡像還暫時不能用。你可能會遇到一個錯誤:a missing Jenkins user,這是OK的。因為我們修改基礎鏡像為Debian操作系統,我們暫時刪除了創建用戶的Jenkins鏡像。
通過這樣的安裝方式,我們基本上吸收了Buildpack鏡像和Java鏡像。那么,剩下的就是將Jenkins鏡像吸收進我們的主鏡像。
首先,安裝Tini(你可以從它的 GitHub了解Tini的更多信息)。Cloudbees推薦使用Tini來管理Jenkins容器中的子進程,因此我們將會在Dockerfile中保留它:
# Install Tini ENV TINI_SHA 066ad710107dc7ee05d3aa6e4974f01dc98f3888Use tini as subreaper in Docker container to adopt zombie processes
RUN curl -fL https://github.com/krallin/tini/releases/download/v0.5.0/tini-static -o /bin/tini && chmod +x /bin/tini \ && echo "$TINI_SHA /bin/tini" | sha1sum -c -</pre>
安裝Tini之后,我們將所有額外的環境變量集中在一起:# SET Jenkins Environment Variables ENV JENKINS_HOME /var/jenkins_home ENV JENKINS_SLAVE_AGENT_PORT 50000 ENV JENKINS_VERSION 1.625.2 ENV JENKINS_SHA 395fe6975cf75d93d9fafdafe96d9aab1996233b ENV JENKINS_UC https://updates.jenkins-ci.org ENV COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log ENV JAVA_OPTS="-Xmx8192m" ENV JENKINS_OPTS="--handlerCountStartup=100 --handlerCountMax=300 --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war"
在這里,我們添加了兩個新的環境變量,JAVA_OPTS和JENKINS_OPTS。同時,我也設置了Cloudbees鏡像中用來安裝Jenkins的所有環境變量。
接下來,為了安裝Jenkins,我會做三件事:創建Jenkins用戶、創建一個卷掛載點和設置初始化目錄。
# Jenkins is run with userjenkins
, uid = 1000If you bind mount a volume from the host or a data container,
ensure you use the same uid
RUN useradd -d "$JENKINS_HOME" -u 1000 -m -s /bin/bash jenkins
Jenkins home directory is a volume, so configuration and build history
can be persisted and survive image upgrades
VOLUME /var/jenkins_home
/usr/share/jenkins/ref/
contains all reference configuration we wantto set on a fresh new installation. Use it to bundle additional plugins
or config file with your custom jenkins Docker image.
RUN mkdir -p /usr/share/jenkins/ref/init.groovy.d</pre>
在這里,我們可以運行CURL命令來下載正確的jenkins.war文件。注意,這里使用了環境變量JENKINS_VERSION,所以如果你以后想修改版本,只要修改環境變量就好了。# Install Jenkins RUN curl -fL http://mirrors.jenkins-ci.org/war-stable/$JENKINS_VERSION/jenkins.war -o /usr/share/jenkins/jenkins.war \ && echo "$JENKINS_SHA /usr/share/jenkins/jenkins.war" | sha1sum -c -
接下來,我會處理相關的目錄和用戶權限。這些和之前的教程中的jenkins-master鏡像是相同的,我們希望能夠更好地隔離我們的Jenkins安裝,并與我們的數據卷容器兼容。我們把從Cloudbees鏡像中引入了jenkins/ref目錄。
# Prep Jenkins Directories RUN chown -R jenkins "$JENKINS_HOME" /usr/share/jenkins/ref RUN mkdir /var/log/jenkins RUN mkdir /var/cache/jenkins RUN chown -R jenkins:jenkins /var/log/jenkins RUN chown -R jenkins:jenkins /var/cache/jenkins
接下來,我會暴露我們需要的端口:
# Expose Ports for web and slave agents EXPOSE 8080 EXPOSE 50000
剩下的,就是復制Cloudbees鏡像中的utility文件,設置Jenkins用戶,并運行startup命令。根據Dockerfile最佳實踐,我們把COPY命令放在一起。這些是很可能改變的。這樣一來,一旦他們改變了,也不會使文件緩存失效。
# Copy in local config files COPY init.groovy /usr/share/jenkins/ref/init.groovy.d/tcp-slave-agent-port.groovy COPY jenkins.sh /usr/local/bin/jenkins.sh COPY plugins.sh /usr/local/bin/plugins.sh RUN chmod +x /usr/local/bin/plugins.sh RUN chmod +x /usr/local/bin/jenkins.sh
注意:直到我們把這些文件拷貝到我們的倉庫為止,我們都不會構建Dockerfile。在下一部分,我們會測試說有內容。特別注意,我添加了chmod +x命令,這保證了添加的文件是可執行的。最后,設置Jenkins用戶和入口點。
# Switch to the jenkins user USER jenkinsTini as the entry point to manage zombie processes
ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/jenkins.sh"]</pre>
你可以在托管在Github上的 教程上找到完整的Dockerfile。現在,讓我們來測試一下所有的修改。注意,當運行到COPY命令的時候,出錯是正常的。docker build jenkins-master/
一切都如預期一樣,包括缺少shell腳本的錯誤。這是我們需要處理的最后一件事。
打開 Cloudbees Jenkins Dockerfile GitHub倉庫。我們需要拷貝這三個文件:
- init.groovy
- plugins.sh
- jenkins.sh
</ol>
下載或復制這些文件,并放在jenkins-master目錄中。
- init.groovy Jenkins啟動時會運行這個groovy文件,它的上下文環境就是Jenkins WAR。通過設置groovy文件,可以保證Jenkins每次啟動都使用相同的配置,即使是第一次安裝。
- plugins.sh 這個腳本會自動下載一個插件列表文件中的所有插件。你可以自己包含這個文件。在以后的博客中,我會用這個腳本來安裝像Docker-plugin這樣的插件。
- jenkins.sh 這是啟動Jenkins的shell腳本,使用了JAVA_OPTS和JENKINS_OPTS兩個環境變量。
</ol>
Cloudbees提供一系列很有用的腳本,所以我建議你保留它們。我們創建自己的Dockerfile的缺點是,如果Cloudbees決定更新他們的鏡像的話,我們的鏡像不會自動更新。如果你想使用這些更新的話,你需要時刻注意Cloudbees所做的更新。
現在,jenkins-master鏡像的Dockerfile準備好了。如果你學習了這個系列教程的話,你已經有一個makefile,并安裝了docker-compose。下一步就是構建最終的鏡像,并啟動Jenkins應用。
docker-compose build docker-compose up -d
- 打開http://yourdockermachineip
- Jenkins應該已經啟動了
</ul>
(http://engineering.riotgames.c ... se.gif)
從現在開始,你已經完全控制了Docker鏡像,當然,使用的OS還是公開的OS鏡像。從頭構建操作系統鏡像,已經超出了本文的范圍。
如果你感興趣的話,基于CentOS7的鏡像也可以在我們的 Github倉庫中找到。使用CentOS或者Debian都是可以的,它們最大的不同就是CentOS使用yum來安裝程序,而不是用apt-get。
結論
完全控制你的Docker鏡像并沒有那么難。首先,需要注意你的依賴,以及它們是從哪里引入的,是如何構成你的容器的。其次,你可以刪除一些不需要的東西,從而節省一些磁盤空間,使你的鏡像更輕一些。你也可以降低其他依賴失效的風險。
另一方面,你的責任也更大了——你再也不會獲得自動更新,你必須自己追蹤Cloudbees Jenkins鏡像的更新。這是否有利取決于你個人的開發策略。
無論你是否選擇控制自己的鏡像,我都建議你遵循同樣的流程。在Dockerhub上,找到你繼承的Docker鏡像,來幫助你理解繼承關系。理解 這條繼承鏈上的所有Dockerfile做了些什么。你應該了解你的鏡像中究竟包含了什么,畢竟它是跑在你的服務器上。至少,你可以從中了解使用的基礎操 作系統、Docker鏡像的生態以及一些有趣的實踐,比如Cloudbees如何使用Tini來管理子進程。
像往常一樣,你可以在GitHub上找到本文的所有 資源。在這上面,有很多不錯的討論。歡迎你們進行評論或者提問。
現在,你已經有了一個完整功能的Jenkins主服務器和環境。下一篇教程會講解如何將從節點連接至主服務器。特別的是,我們會基于容器來構建從服務器。有很多種不同的方式來做這件事,我們會討論其中的幾種。
原文鏈接:TAKING CONTROL OF YOUR DOCKER IMAGE(翻譯:夏彬 校對:李穎杰)
來自:http://dockone.io/article/913