官术网_书友最值得收藏!

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的方式來實現應用容器化。

主站蜘蛛池模板: 齐齐哈尔市| 四子王旗| 濉溪县| 珠海市| 开封市| 瓦房店市| 灵武市| 鄂州市| 肃宁县| 古蔺县| 唐海县| 梁平县| 容城县| 余江县| 额济纳旗| 阳东县| 石林| 临泽县| 怀安县| 马山县| 紫阳县| 望奎县| 吴桥县| 若尔盖县| 留坝县| 平江县| 和平县| 东方市| 汉源县| 南乐县| 瓦房店市| 灌云县| 赤城县| 波密县| 莒南县| 塔河县| 永川市| 莆田市| 涿鹿县| 古丈县| 缙云县|