docker(四)--Dockerfile


 

鏡像制作
1,基於容器制作
2,dockerfile


docker 語法格式

  • # comment 注釋信息
  • INSTRUCTION arguments 指令與參數,通常指令都是大寫

docker是自上而下順序執行的,第一個非注釋行必須是'FROM'指令,基於哪個基礎鏡像來做的

 

Dockerfile
1,需要有一個專用工作目錄
2,dockerfile放在這個工作目錄下,且文件名首字母大寫
3,dockerfile如果要打包其他文件到鏡像,這些文件的路徑必須是當前工作目錄或下級子目錄,不能是父目錄
4,dockerfile支持制作一個隱藏文件.dockeringore,這個文件中可以寫入文件路徑,在打包時,所有寫在.dockeringore中的文件都不包含進去。 例如要打包一個目錄下的文件,但不包含其中某3個文件,可以將這3個文件的路徑寫入.dockeringore。那么.dockeringore本身以及里面包含的文件,都不會被打包進去。

變量

變量引用:$variable_name  ${variable_name}
${variable:-word}   如果變量沒設置或值為空,就使用word字串,否則就返回變量值。用得較多

[root@abao ~]# echo ${NAME:-lily}
lily
[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:-lily}
jerry

${variable:+word} 變量有值就顯示word, 沒值就沒

[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:+tom}
tom
[root@abao ~]# unset NAME
[root@abao ~]# echo ${NAME:+tom}

 

Dockerfile常用指令 

FROM  最重要的一個指令,且必須為Dockerfile文件開篇的第一個非注釋行,用於指定基准鏡像,后續的指令運行於基准鏡像提供的運行環境。
基准鏡像可以是任何可用鏡像文件,默認情況下,docker build會在docker主機上查找指定的鏡像文件,當其不存在時,則從Docker Hub上拉取
語法:
- FROM <repository>[:<tag>]或
- FROM <repository>@<digest> # <repository>:指定作為base image的名稱,digest指定hash碼

MAINTAINER (可選,已廢棄被LABLE代替)    用於讓Dockerfile制作者提供本人的信息

LABLE  為鏡像指定各種元數據。   語法:LABEL <key>=<value> <key>=<value> <key>=<value> ... 鍵值數據

COPY   從宿主機把文件復制到目標鏡像中
語法:
- COPY <src> ..<des> 或
- COPY ["<src>",..."<dest>"]
<src>: 要復制的源文件或目錄,支持使用通配符(一般是當前工作目錄)
<dest>: 目標路徑,即正在創建的image的文件系統路徑,建議為<dest>使用絕對路徑,否則以WORKDIR為其起始路徑
注:在路徑中有空白字符時,通常使用第二種格式

文件復制准則
- <src> 必須是build上下文中的路徑,不能是其父目錄的文件
- <src> 如果是目錄,<src>目錄自身不會被復制,其內部文件或子目錄都會被復制。 (目錄自身不會被復制,如cp -r tmp/* /tmp)
- 如果指定了多個<src>,或在<src>中使用了通配符,則<dest>必須是一個目錄,且必須以/結尾,不加會報錯
- 如果<dest>事先不存在,它將會自動創建

COPY index.html /data/web/html/      # index.html一定要在當前目錄下,或在子目錄下
COPY yum.repos.d /etc/yum.repos.d/     #復制yum.repos.d目錄只是復制它下面的內容,所以要寫明目標目錄名

Dockerfile:

# Description: test image
FROM busybox:latest
#MAINTAINER "abao <abao@163.com>"
LABEL maintainer="abao <abao@163.com>"
COPY index.html /data/web/html/

# docker build -t testhttpd:v1.0 ./
# ls
Dockerfile index.html
# docker run --name tinyweb1 --rm tinyhttpd:v0.1 cat /data/web/html/index.html
<h1>hello, Busybox httpd server image.</h1>

 

ADD    類似於COPY指令,ADD支持使用tar文件和URL路徑
語法:
ADD <src> ...<dest> 或
ADD ["<src>",.."<dest>"]

- 同COPY指令
- <src>為URL且<dest>不以/結尾,則<src>指定的文件將被下載並被創建為<dest>;如果<dest>以/結尾,則URL指定的文件將被下載並保存到<dest>/目錄下
- <src>是一個本地壓縮格式tar文件,它將被展開為一個目錄,類似於"tar -x"命令;  但是,通過URL獲取的tar文件不會自動展開 
- <src>有多個,或使用了通配符,則<dest>必須是一個以/結尾的目錄路徑,如果<dest>不以/結尾,則其被視作一個普通文件,<src>的內容被直接寫入到<dest>;

WORKDIR  用於為Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定設定工作目錄

  語法: WORKDIR <dirpath>

 

volume  用於在image中創建一個掛載點目錄,用來掛載Docker host上的卷或其它容器上的卷
語法: VOLUME <mountpoint> 或 VOLUME |”<mountpoint>"
如果掛載點目錄路徑下此前在文件存在,docker run命令會在卷掛載完成后將此前所有文件復制到新掛載的卷中
卷有兩種格式:綁定掛載卷和docker管理卷,dockerfile中只能用docker管理的卷,即指定容器中的路徑,而不能指定宿主機的目錄

VOLUME /data/mysql/
# docker build -t tinyhttpd:v0.5 ./
# docker run --name tinyweb1 --rm tinyhttpd:v0.5 sleep 100
# docker inspect tinyweb1
        "Mounts": [
            {
                "Type": "volume",
                "Name": "a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29",
                "Source": "/var/lib/docker/volumes/a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29/_data",
                "Destination": "/data/mysql",

  

EXPOSE  待暴露端口,用於為容器打開指定要監聽的端口以實現與外部通信 

(只能指定容器暴露的端口,再隨機綁定宿主機的端口,不能指定宿主機綁定的端口)
語法: 
    EXPOSE <port>[/<protocol>][<port>[/<protocol>]...]
         <protocol> 用於指定傳輸層協議,可為tcp或udp二者之一,默認為TCP協議
         EXPOSE指令可一次指定多個端口,例如EXPOSE 112/udp 112/tcp

 注意:寫在dockerfile中的端口暴露並不會直接暴露,使用docker run中的-P選項時,不用指定,會自動讀取鏡像中要暴露那個端口

EXPOSE 80/tcp
# docker build -t tinyhttpd:v0.6 ./
# docker run --name tinyweb1 --rm tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html
# docker inspect tinyweb1   查看容器的ip
# curl 172.17.0.3
<h1>hello, Busybox httpd server image.</h1>
# docker port tinyweb1   #實際查詢沒有暴露端口
# docker kill tinyweb1
tinyweb1
# docker run --name tinyweb1 --rm -P tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html 
# docker port tinyweb1
80/tcp -> 0.0.0.0:32769
可以在外部訪問宿主機:32769 

 

ENV  用於為鏡像定義所需要的環境變量,並可被Dockerfile文件中位於其后的其它指令(如ENV、ADD、COPY等)所調用,調用格式為$variable_name}或$variable_name

語法:
ENV <key> <value>    #一次只能設置一個變量,<key>之后所有內容均會被視作其<value>的組成部分
ENV <key>=<value>...   # 一次可設置多個變量,每個變量為一個"<k>=<v>"的鍵值對,如果<value>中有空格,可以用反斜線\轉義,也可用<value>引號進行標識;另外,反斜線也可用於續行。當定義多個變量時,建議使用這種方式,以便在同一層中完成所有功能

例如:

ENV DOC_ROOT=/data/web/html/ \
    WEB_SERVER_PACKAGE="nginx-1.17.8"
COPY index.html ${DOC_ROOT:-/data/web/html/}
WORKDIR /usr/local/src/
ADD ${WEB_SERVER_PACKAGE}.tar.gz ./

# docker build -t tinyhttpd:v0.7 ./
# docker run --name tinyweb1 --rm tinyhttpd:v0.7 ls /usr/local/src/
nginx-1.17.8
# docker run --name tinyweb1 --rm tinyhttpd:v0.7 cat /data/web/html/index.html
<h1>hello, Busybox httpd server image.</h1>
# docker run --name tinyweb1 --rm tinyhttpd:v0.7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=1c99b2e89674
DOC_ROOT=/data/web/html/
WEB_SERVER_PACKAGE=nginx-1.17.8
HOME=/root

 在docker run時 通過-e 選項 也可指定變量

# docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=e13da61e21b2
WEB_SERVER_PACKAGE=nginx-1.14.0
DOC_ROOT=/data/web/html/
HOME=/root
# docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 ls /usr/local/src/
nginx-1.17.8
#可以看到docker run時可以修改環境變量的值,但是並不會改變docker build過程

 

 RUN    用於 docker build 過程中運行的命令

ENV DOC_ROOT=/data/web/html/ \
    WEB_SERVER_PACKAGE="nginx-1.17.8.tar.gz"
ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/
WORKDIR /usr/local/src/ RUN cd /usr/local/src && \ tar -xf ${WEB_SERVER_PACKAGE} && \ mv nginx-1.17.8 webserver

# docker build -t tinyhttpd:v0.8 ./
# docker run --name tinyweb1 --rm -it tinyhttpd:v0.8
/usr/local/src # ls
nginx-1.17.8.tar.gz  webserver

Syntax:

  • RUN <command> 或
  • RUN ["<executable>","<param1>","<param2>"]    #注意要使用雙引號,單引號可能會出錯

- 第一種格式,<command>通常是一個shell命令,且以"/bin/sh -c"來運行它,當作shell子進程來運行,這意味此進程在容器的PID不為1,不能接收Unix信號,因此,當使用docker stop <container>命令停止容器時,此進程接收不到SIGTERM信號。

- 第二種中的參數是一個JSON格式的數組,其中<executable>為要運行命令,后面<paramN>為傳遞給命令的選項或參數;然而,命令不會以"/bin/sh -c"來發起,也就是直接由內核來創建,因此常見的shell操作如變量替換以及通配符(?,*等)替換將不會進行; 不過,如果想使用shell來操作,可以使用以下格式:RUN ["/bin/bash","-c","<exectuable>","<param1>"]

 

CMD   用於定義把鏡像啟動為容器時(docker run) 默認運行的命令

 

-  類似於RUN指令,CMD指令也可用於運行任何命令或應用程序,不過,二者的運行時間點不同,RUN指令運行於映像文件構建過程中,而CMD指令運行基於Dockerfile構建出新映像文件 啟動一個容器時

-  CMD指令的首要目的在於為啟動容器指定默認要運行的程序,且其運行結束后,容器也將終止;不過,CMD指定的命令其可以被docker run的命令選項所覆蓋
- 在Dockerfile中可以存在多個CMD指令,但僅最后一個會生效
語法:

  • CMD <command> #  自動運行為shell子進程,最大的壞處是id不為1,無法使用docker stop去停止,因為它接收不到信號,接信號都是進程號為1的進程,因為它是一個超管進程,可以讓它為1
  • CMD ["<executable>","<param1>","<param2>"]     #  直接啟動為用戶id為1的進程,可以接收處理shell的信號
  • CMD ["<param1>","<param2>"]   # 用於 ENTRYPOINT 指令提供默認參數

測試第一種方法:

FROM busybox
LABEL maintainer="abao <abao@163.com>" app="httpd"

ENV WEB_DOCROOT="/data/web/html/"

RUN mkdir -p ${WEB_DOCROOT} && \
    echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html

CMD /bin/httpd -f -h ${WEB_DOCROOT}
#啟動容器時,應該是作為shell的子進程來運行,pid不為1

#創建鏡像
 docker build -t tinyhttpd:v1.0 ./
# 查看鏡像 
docker image inspect tinyhttpd:v1.0
        "Cmd": [
            "/bin/sh",    #可以看到默認會以"/bin/sh -c"來運行它 "-c",
            "/bin/httpd -f -h ${WEB_DOCROOT}"
        ],
# 啟動容器
  docker run --name tinyweb1 -it --rm -P  tinyhttpd:v1.0
沒有進入交互式,# 鏡像中定義默認運行的程序是httpd,是shell的子進程,不是shell,沒有交互式接口

# 使用exec進入交互式
]# docker exec -it tinyweb1 /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/httpd -f -h /data/web/html/  # 這里id為1是因為默認執行了exec的替換,為了確保容器能自動接收unix的信號,執行docker stop時能停止,執行docker kill時能殺掉
    6 root      0:00 /bin/sh
   11 root      0:00 ps
/ # printenv
HOSTNAME=5b27cc8a100f
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
WEB_DOCROOT=/data/web/html/
PWD=/
/ # 

 

第二種方式:

CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"]

#制作鏡像
docker build -t tinyhttpd:v1.1 ./
# 查看鏡像
docker image inspect tinyhttpd:v1.1
            "Cmd": [
                "/bin/httpd",
                "-f",
                "-h ${WEB_DOCROOT}"
            ],
# 啟動容器
 docker run --name tinyweb1 -it --rm -P  tinyhttpd:v1.1
httpd: can't change directory to ' ${WEB_DOCROOT}': No such file or directory
#會報錯,因為程序不會運行為shell子進程,而變量是為shell變量,所以無法識別

 

ENTRYPOINT

# docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 ls /data/web/html index.html
# docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 httpd -f -h /data/web/html/
# 在dockerfile中指定的默認要運行的程序是httpd,但是docker run時可以自己指定命令(ls /data/web/html和httpd ...等,覆蓋鏡像中默認要運行的命令

 

- ENTRYPOINT可以實現類似於CMD指令的功能,用於為容器指定默認運行程序,從而使得容器像是一個單獨的可執行程序
- 與CMD不同的是,由ENTRYPOINT啟動的程序不會被docker run命令行指定的參數所覆蓋,而且,這些命令行參數會被當參數傳遞給ENTRYPOINT指定的程序
- 不過docker run命令中的 --entrypoint選項的參數 可覆蓋ENTRYPOINT指令指定的程序

Syntax:

- ENTRYPOINT <command>
- ENTRYPOINT ["<executable","<param1>","<param2>"]

   docker run 命令傳入的命令參數會覆蓋CMD指令的內容並且附加到ENTRYPOINT命令最后做為其參數使用
   Dockerfile文件中也可以存在多個ENTRYPOINT指令,但僅有最后一個會生效

FROM busybox
LABEL maintainer="abao <abao@163.com>" app="httpd"

ENV WEB_DOCROOT="/data/web/html/"

RUN mkdir -p ${WEB_DOCROOT} && \
    echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html

ENTRYPOINT /bin/httpd -f -h ${WEB_DOCROOT}

構建鏡像並啟動容器,測試:
docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.3 ls /data/web/html

#並沒有執行ls,而是執行的/bin/httpd -f -h /data/web/html ls /data/web/html。 把ls...當作參數附加在命令后面

 

CMD的第三種方式:

同時有 CMD 和 ENTRYPOINT 時,CMD指定的選項會用於為ENTRYPOINT提供默認參數

FROM busybox
LABEL maintainer="abao <abao@163.com>" app="httpd"

ENV WEB_DOCROOT="/data/web/html/"

RUN mkdir -p ${WEB_DOCROOT} && \
    echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html

CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"] ENTRYPOINT ["/bin/sh","-c"] #默認會執行 /bin/sh -c /bin/httpd -f -h /data/web/html
docker build -t tinyhttpd:v1.3 ./ docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.4 "ls /data/web/html" index.html #發現執行了 ls 命令,因為CMD的參數會被當成ENTRYPOINT的默認參數,而命令行參數會被當參數傳給ENTRYPOINT,所以CMD的參數被覆蓋了。執行的命令是 /bin/sh -c ls /data/web/html

 

 

配置nginx示例:

# cat Dockerfile 
FROM nginx:1.14-alpine
LABEL maintainer="abao <abao@abao.163.com>"

ENV NGX_DOC_ROOT="/data/web/html/"
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]

# cat entrypoint.sh 
#!/bin/sh
cat > /etc/nginx/conf.d/www.conf << EOF
server {
    server_name ${HOSTNAME};
    listen ${IP:-0.0.0.0}:${PORT:-80};
    root ${NGX_DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@" 

chmod +x entrypoint.sh
echo " <h1> New doc root for nginx.</h1>" > index.html

docker build -t myweb:v0.5 ./   
docker run --name myweb1 --rm -P  myweb:v0.5  
docker exec -it myweb1 /bin/sh 
/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process /usr/sbin/nginx -g daemon off;
    8 nginx     0:00 nginx: worker process
    9 root      0:00 /bin/sh
   14 root      0:00 ps -ef
/ # cat /etc/nginx/conf.d/www.conf 
server {
    server_name 790db97efd5a;
    listen 0.0.0.0:80;
    root /data/web/html/;
}
/ # wget -O - -q 790db97efd5a    #訪問nginx
<h1> New doc root for nginx.</h1>
docker run時傳遞環境變量 docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.5 docker exec -it myweb1 /bin/sh / # ps PID USER TIME COMMAND 1 root 0:00 nginx: master process /usr/sbin/nginx -g daemon off; #pid為1,因為entrypoint.sh中執行命令是exec 8 nginx 0:00 nginx: worker process 9 root 0:00 /bin/sh 14 root 0:00 ps / # cat /etc/nginx/conf.d/www.conf server { server_name 0a9b161911b8; listen 0.0.0.0:8080; root /data/web/html/; } / # wget -O - -q 0a9b161911b8:8080 <h1> New doc root for nginx.</h1>

 通常情況下,可以使用 entrypoint腳本把需要修改的部分,如IP,PORT,作為變量放在配置文件中,讓腳本可以通過使用這些環境變量生成配置文件,在docker run時用環境變量給它傳值。

 

USER

用於指定運行image時的或運行Dockerfile中任何RUN、CMD或ENTRYPOINT指定的程序的用戶名或UID
默認情況下,container的運行身份為root用戶

語法:
    USER <UID>|<UserName>
     需要注意的是,<UID>可以為任意數字,但是實踐中其必須為/etc/passwd中某用戶的有效UID,否則docker run命令將運行失敗

 

HEALTHCHECK
 不依據主進程運行與否來判斷健康與否,而是檢測是否真正能提供服務,使用wget或curl查看是否能加載頁面。

語法:

HEALTHCHECK [OPTIONS] CMD command   (通過運行一個command來檢查容器主進程是否健康)
HEALTHCHECK NONE      (拒絕任何健康狀態檢測,包括默認的檢測機制)

OPTIONS:
--interval=DURATION(default: 30s) #檢測間隔時間
--timeout=DURATION(default: 30s) #超時時間
--start-period=DURATION(default: 0s) #容器啟動時,可能進程服務還沒初始化完成,這時去檢查會是報錯失敗的,檢測可以等待一段時間,像tomcat程序啟動時可能需要10s
--retries=N(default: 3) #重試次數

響應結果:
0:success
1: unhealthy
2: reserved 預留,自定義

示例:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost || exit 1 

FROM nginx:1.14-alpine
LABEL maintainer="abao <abao@abao.163.com>"

ENV NGX_DOC_ROOT="/data/web/html/"

ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/

EXPOSE 80/tcp
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/

CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]

# docker run --name myweb1 --rm -P  -e "PORT=8080" myweb:v0.6
127.0.0.1 - - [25/Feb/2020:03:16:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:17:23 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:17:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:18:24 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"

 

SHELL    定義運行程序默認使用的shell

語法: SHELL ["executable","parameters"]
默認linux是["/bin/sh","-c"],windows是["cmd","/S","/C"]

STOPSIGNAL

進程號id 為 1 的,可以接收 docker stop 命令,從而能夠停止主進程,從而停止容器。如 stop 發送的是 15,signal 是無符號整型數字(必須匹配 kernal 的 syscall table),如 9 ,也可以是 SIGNAME,如 SIGKILL
Syntax: STOPSIGNAL signal

ARG

 在 docker build 過程中傳參數,使用  --build-arg <varname>=<value> 

FROM nginx:1.14-alpine
ARG author="abao <abao@163.com>"
LABEL maintainer=${author}

ENV NGX_DOC_ROOT="/data/web/html/"

ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/

EXPOSE 80/tcp
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/

CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
# docker build --build-arg author="xiao <xiao@qq.com>" -t myweb:v0.9 ./
# docker image inspect myweb:v0.9 |grep maintainer
                "maintainer": "xiao <xiao@qq.com>"
                "maintainer": "xiao <xiao@qq.com>"

 

 ONBUILD  用於在 Dockerfile 中定義一個觸發器

Dockerfile用於 build 鏡像文件,此鏡像文件可以作為 base image 被另一個 Dockerfile 用做 FROM 指令的參數,並以只構建新的鏡像文件
在后面的這個Dockerfile中的 FROM 指令在 build 過程中被執行時,將會“觸發”創建其 base image 的 Dockerfile 文件中 ONBUILD 指令定義的觸發器

    base image->build -> image1 -> build(這個過程中執行ONBUILD)->image2

Syntax: ONBUILD <INSTRUCTION>

  • 盡管任何指令都可以注冊成為觸發器指令,但ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER 指令
  • 使用包含 ONBUILD 指令的 Dockerfile 構建的鏡像應該使用特殊的標簽,例如 ruby:2.0-onbuild
  • 在 ONBUILD 指令中使用 ADD 或 COPY指令應該格外小心,因為新構建過程的上下文在缺少指定原文件時會失敗

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM