- OpenShift在企業中的實踐:PaaS DevOps微服務(第2版)
- 魏新宇 郭躍軍
- 2685字
- 2021-11-05 10:17:24
4.2.5 本地構建實現應用容器化
1.本地構建命令介紹
我們知道Dockerfile會自動構建容器鏡像。Dockerfile是一個文本文件,其中含有一組可用來構建容器鏡像的命令。接下來,我們分別介紹常用的命令,以便讀者能對使用Dockerfile實現應用容器化有較深的理解。
我們知道容器鏡像是分層管理的,這樣做的好處是容器的系統介質可以實現精簡化,打包方便。在鏡像中只有文件系統的最頂層是可讀寫的,其余均為只讀,因此在書寫Dockerfile的時候不要引入過多鏡像層級,以控制鏡像的大小。我們秉承這個原則來看如何使用命令編寫Dockerfile。
(1)RUN命令
RUN命令會在當前鏡像上創建一個新的鏡像層執行命令。RUN命令會增加鏡像的層數,所以在Dockerfile中執行RUN的時候使用&&命令分隔符在單個RUN指令中執行多個命令,以控制鏡像的大小。
舉例來說,下面是一種不好的寫法:
RUN yum update RUN yum install -y httpd RUN yum clean all -y
為了控制容器鏡像的層數,我們應該將其調整為:
RUN yum update && \ yum install -y httpd && \ yum clean all -y
(2)LABEL命令
LABEL命令可定義鏡像元數據(鍵值對格式)。LABEL命令通常用來為鏡像添加描述性元數據,如版本、描述信息等,這樣后面使用者可以了解鏡像的相關信息。LABEL命令也會增加鏡像的層數,如果我們要指定多個數值,建議對所有標簽使用一條指令。
運行在OpenShift上的鏡像需要定義一些特殊的標簽,OpenShift可以解析標簽,并基于這些標簽的存在性來執行某些操作。
如果想了解Labels的使用方式,我們不妨以紅帽提供的Builder鏡像的Dockerfile為參考。
LABEL \ com.redhat.component="jboss-webserver-3-webserver31-tomcat8-openshift-container" \ description="Red Hat JBoss Web Server 3.1 - Tomcat 8 OpenShift container image" \ io.cekit.version="2.2.7" \ io.k8s.description="Platform for building and running web applications on JBoss Web Server 3.1 - Tomcat v8" \ io.k8s.display-name="JBoss Web Server 3.1" \ io.openshift.expose-services="8080:http" \ io.openshift.s2i.destination="/tmp" \ io.openshift.s2i.scripts-url="image:///usr/local/s2i" \ io.openshift.tags="builder,java,tomcat8" \ name="jboss-webserver-3/webserver31-tomcat8-openshift" \ org.concrt.version="2.2.7" \ org.jboss.container.deployments-dir="/deployments" \ summary="Red Hat JBoss Web Server 3.1 - Tomcat 8 OpenShift container image" \ version="1.4"
(3)WORKDIR命令
WORKDIR命令為Dockerfile中的命令(RUN、CMD、ENTRYPOINT、COPY或ADD)設置工作目錄。
建議在WORKDIR命令中使用絕對路徑。在Dockerfile中切換路徑時要使用WORKDIR,不要使用RUN,這樣有助于提升鏡像的可維護性,后續進行問題診斷也會更方便。
(4)ENV命令
ENV命令定義了容器可用的環境變量。我們可以在Dockerfile中聲明多個ENV命令,還可以在運行容器中使用ENV命令查看這些環境變量。在Dockerfile中通常使用ENV命令來定義文件和文件夾路徑,不要使用ENV命令進行硬編碼。
ENV命令會增加鏡像的層數,如果我們要指定多個數值,建議使用一條指令設置所有環境變量,并用等號(=)分隔每個鍵值對。
如:
ENV MYSQL_ROOT_PASSWORD="my_password" \ MYSQL_DATABASE="my_database"
(5)USER命令
USER命令指定運行命令的用戶名和組名(如RUN、CMD和ENTRYPOINT等命令)。出于安全原因,我們建議以非root用戶身份運行鏡像。同樣,為了減少鏡像的層數,要避免在Dockerfile中多次使用USER命令。
默認情況下OpenShift使用任意分配的User ID運行容器。這種方法減輕了容器中運行的進程在主機上獲得升級權限的風險。
當我們書寫Dockerfile時,針對OpenShift的特點,需要考慮以下問題:
·如果容器中的進程想訪問容器內的目錄或文件,需要將這些文件或目錄的屬組設置為root group。
·容器中的可執行文件具有group執行權限。
·容器中運行的進程不得偵聽特權端口(即1024以下的端口)。
我們將運行容器的用戶設置為root group,然后通過在Dockerfile中添加以下RUN命令可以設置目錄和文件權限,這樣root group中的用戶就有權限訪問這些目錄。
RUN chgrp -R 0 directory && \ chmod -R g=u directory
上面chmod命令中的g=u的作用是將owner權限賦給group,也就是rwx權限。
由于root組不具備root用戶的特殊權限,因而通過將目錄設置為root group的方式可以避免直接使用root用戶運行容器,降低了安全風險。
在某些情況下我們無法獲取有的鏡像的原始Dockerfile,也無法重新構建。如果鏡像在構建過程中定義使用了root用戶執行某些命令,這時候我們需要以root用戶的身份來運行此類鏡像。在這種情況下需要配置OpenShift的SCC以允許容器以root用戶的身份來運行。
(6)ONBUILD命令
ONBUILD命令會在容器鏡像中注冊Triggers。Dockerfile僅在構建子鏡像時執行ONBUILD聲明的指令。ONBUILD對于支持容器鏡像的自定義很重要。我們可以用它將應用包嵌入容器鏡像中。
例如,我們構建一個Node.js父鏡像,并希望所有開發人員都將其用作基礎鏡像,這個基礎鏡像需要滿足如下要求:
·可以將JavaScript源代碼復制到應用文件夾中,以便Node.js引擎可以讀取。
·執行npm install命令,以獲取package.json文件中描述的所有依賴關系。
我們通過在Dockerfile中聲明ONBUILD完成需求。
FROM registry.access.redhat.com/rhscl/nodejs-6-rhel7 EXPOSE 3000 # Mandate that all Node.js apps use /usr/src/app as the main folder (APP_ROOT). RUN mkdir -p /opt/app-root/ WORKDIR /opt/app-root # Copy the package.json to APP_ROOT ONBUILD COPY package.json /opt/app-root # Install the dependencies ONBUILD RUN npm install # Copy the app source code to APP_ROOT ONBUILD COPY src /opt/app-root # Start node server on port 3000 CMD [ "npm", "start" ]
當上面定義的父鏡像構建成功以后(例如叫mynodejs-base),我們就可以在其他Dockerfile中引用它,如:
FROM mynodejs-base RUN echo "Started Node.js server..."
當子鏡像構建時它會觸發父鏡像中定義的三個ONBUILD命令。
2.本地構建實現應用容器化案例分析
構建一個Apache HTTP的鏡像,要求如下:
·紅帽提供的RHEL7鏡像作為基礎鏡像。
·生成的鏡像名稱為david/httpd-parent。
·RUN命令包含安裝Apache HTTP服務器的幾個命令,并為Web服務器創建默認主頁。
·ONBUILD命令允許子鏡像在構建從父鏡像擴展而來的鏡像時提供自己定制的Web服務器內容。
·USER命令以root用戶身份運行Apache HTTP服務器進程。
Dockerfile的內容如下。
FROM registry.access.redhat.com/rhel7/rhel # Generic labels LABEL Component="httpd" \ Name="david/httpd-parent" \ Version="1.0" \ Release="1" # Labels consumed by OpenShift LABEL io.k8s.description="A basic Apache HTTP Server image with ONBUILD instructions" \ io.k8s.display-name="Apache HTTP Server parent image" \ io.openshift.expose-services="80:http" \ io.openshift.tags="apache, httpd" # DocumentRoot for Apache ENV DOCROOT=/var/www/html \ LANG=en_US \ LOG_PATH=/var/log/httpd RUN yum install -y --setopt=tsflags=nodocs --noplugins httpd && \ yum clean all --noplugins -y && \ # Allows child images to inject their own content into DocumentRoot ONBUILD COPY src/ ${DOCROOT}/ EXPOSE 80 # This stuff is needed to ensure a clean start RUN rm -rf /run/httpd && mkdir /run/httpd # Run as the root user USER root # Launch apache daemon CMD /usr/sbin/apachectl -DFOREGROUND
我們使用docker build或podman build命令,將david/httpd-parent鏡像構建成功。接下來使用david/httpd-parent作為基礎鏡像來實現應用容器化。
構建成功的david/httpd-parent鏡像在運行時需要注意以下兩點:
·在父Dockerfile中使用了HTTP服務80端口。由于OpenShift使用隨機User ID運行容器,低于1024的端口是特權端口,只能以root身份運行。
·OpenShift運行容器使用的隨機User ID對/var/log/httpd沒有讀寫權限。
針對這種情況,我們有兩種解決方法。第一種方法是前面提到的關聯特殊的SCC運行,步驟如下。
# oc project container-build # oc create serviceaccount apacheuser # oc adm policy add-scc-to-user anyuid -z apacheuser # oc patch dc/hello --patch \ '{"spec":{"template":{"spec":{"serviceAccountName": "apacheuser"}}}}'
另外一種方法是在子Dockerfile引用父Dockerfile時進行權限和端口的覆蓋。我們將子Dockerfile和源碼一起放到Git上。
書寫引用父鏡像的Dockerfile,對父鏡像的變更如下:
·覆蓋父鏡像的EXPOSE指令并將端口更改為8080。另外,覆蓋io.openshift.expose-service標簽以指明Web服務器運行在8080端口。
·在非特權端口(即大于1024的端口)上運行Web服務器。使用RUN命令將Apache HTTP服務器配置文件中的端口號從默認端口80更改為8080。
·更改Web服務器進程讀寫文件的文件夾的組ID和權限。
·通過USER命令添加普通用戶,這里我們使用User ID 1001。
修改后的Dockerfile內容如下。
FROM registry.example.com:5000/david/httpd-parent EXPOSE 8080 LABEL io.openshift.expose-services="8080:http" RUN sed -i "s/Listen 80/Listen 8080/g" /etc/httpd/conf/httpd.conf RUN chgrp -R 0 /var/log/httpd /var/run/httpd && \ chmod -R g=u /var/log/httpd /var/run/httpd USER 1001
子容器./src文件夾中提供應用的代碼即index.html文件,該文件將覆蓋父鏡像index.html文件。子容器鏡像的index.html文件的內容如下。
<!DOCTYPE html> <html> <body> DavidWei: Hello from the Apache child container! </body> </html>
使用子Dockerfile構建和部署容器至OpenShift集群。
# oc new-app --name hello \ http://services.example.com/container-build \ --insecure-registry
接下來會自動開始構建,過程大致如下:
·OpenShift從oc new-app命令提供的URL(http://services.example.com/container-build)克隆Git存儲庫。
·Git存儲庫根目錄上的Dockerfile會自動識別,并啟動Docker構建進程。
·父Dockerfile中的ONBUILD命令會觸發子index.html文件的復制,它會覆蓋父索引頁。
·最后,構建的鏡像會推送到OpenShift內部鏡像倉庫。
創建路由。
# oc expose svc/hello --hostname hello.apps.example.com
驗證應用。
# curl http://hello.apps.example.com Dvidwei: Hello from the Apache child container!
通過本案例,我們了解了通過本地構建實現應用容器化的方式。接下來,介紹使用S2I的方式來實現應用容器化。
- SPSS數據挖掘與案例分析應用實踐
- Google Flutter Mobile Development Quick Start Guide
- 我的第一本算法書
- Learning Elixir
- Servlet/JSP深入詳解
- CKA/CKAD應試教程:從Docker到Kubernetes完全攻略
- C++ 從入門到項目實踐(超值版)
- Jenkins Continuous Integration Cookbook(Second Edition)
- Teaching with Google Classroom
- RabbitMQ Cookbook
- Photoshop智能手機APP界面設計
- Instant GLEW
- 計算思維與Python編程
- HTML5 and CSS3:Building Responsive Websites
- 大象:Thinking in UML(第二版)