K8S 系列之 Dockerfile

DokcerFile 鏡像定製

定製 docker 鏡像的方式有兩種:

  1. 手動修改容器內容,導出新的鏡像。

  2. 基於 dockerfile 自行編寫指令,基於指令流程創建鏡像。

Dockerfile 簡介


鏡像是多層存儲,每一層都是在前一層的基礎上進行修改;****容器也是多層存儲,以鏡像爲基礎層,在其基礎上加一層作爲容器運行時的存儲層。剛纔說了,創建鏡像的兩個方法:

  1. 手動修改容器內容,然後 dokcer commit 提交容器爲新的鏡像

  2. 通過在 dockerfile 中定義一系列的命令和參數構建成的腳本,然後這些命令應用於基礎鏡像,依次添加層,最終生成一個新的鏡像。極大的簡化了部署工作。

Dockerfile 主要組成部分

基礎鏡像信息 FROM centos:7.9 
製作鏡像操作指令 RUN yum install -y nginx 
容器啓動時執行指令 CMD ["/bin/bash"]

宿主機直接部署軟件流程與 Dockerfile 部署軟件流程對比

需求 : 安裝一個 mysql,並啓動。

虛擬機部署形式:

  1. 開啓vmware
  
  2. 運行某一個虛擬即,centos7
  
  3. centos7安裝mysql yum install mysql-server
  
  4. 通過腳本或者命令,啓動mysql即可

部署緩慢,且修改了宿主機的環境,刪除較爲麻煩,佔用宿主機的一個3306端口

容器的部署形式:

  1.  開始vmware
  
  2. 運行虛擬機centos7(宿主機)
  
3. 安裝docker容器軟件
  
4. 獲取mysql鏡像即可,docker pull mysql:tag(你無法自由控制,該mysql的基礎鏡像時什麼發行版本,你獲取的鏡像,是別人定製好的,你下載使用的默認時Debian發行版,你希望得到一個基於centos7.9的發行版本,運行mysql)
  
5. 直接運行該鏡像,通過端口映射,運行mysql
  
6. 訪問宿主機對的一個映射端口,訪問到容器內的mysql

想自定義鏡像,就得自己寫腳本,也就是 dockerfile 了

Dokcerfile 指令

FROM  指定基礎鏡像

MAINTAINER  指定維護者信息,可以沒有

RUN  你想讓它幹啥(在命令前面加上RUN即可)

ADD  添加宿主機的文件到容器內,還多了一個自動解壓的功能
# RUN tar -Zxf /opt/xx.tgz 	# 報錯!該tgz文件不存在! !

COPY  作用和ADD是一樣的,都是拷貝宿主機的文件到容器內, COPY就是僅僅拷貝

WORKDIR  相當於cd命令,設置當前工作目錄

VOLUME  設置目錄映射,掛載主機目錄

EXPOSE  指定對外的端口,在容器內暴露一個端口,端口 EXPORT 80

CMD  指定容器啓動後的要乾的事情

ENTRYPOINT	作用和CMD一樣,都是在指定容器啓動程序以及參數。
# 當指定了ENTRYPOINT之後,CMD指令的語義就有了變化,而是把CMD的內容當作參數傳遞給ENTRYPOINT指令。

ARG	 設置環境變量
# ARG只是用於構建鏡像需要設置的變量,容器運行時就消失了

ENV   和ARG一樣,都是設置環境變量
# 區別在於ENV無論是在鏡像構建時,還是容器運行,該變量都可以使用

USER  用於改變環境,用於切換用戶

Dokcerfile 實踐


需求:通過 dockerfile,構建 nginx 鏡像,且運行容器後,生成的頁面是 "辣辣小姐姐"。

1. 創建Dockerfile,注意文件名,必須是這個
[root@docker01 ~]# mkdir /learn_docker
[root@docker01 ~]# cd /learn_docker/
[root@docker01 learn_docker]# vim Dockerfile
FROM nginx
RUN echo "<meta charset=utf-8>辣辣小姐姐" > /usr/share/nginx/html/index.html

2. 構建Dockerfile
[root@docker01 learn_docker]# docker build .

3. 修改鏡像名字
[root@docker01 learn_docker]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
<none>       <none>    950549357c1f   18 seconds ago   133MB
nginx        latest    08b152afcfae   6 days ago       133MB
[root@docker01 learn_docker]# docker tag 950549357c1f my_nginx

4. 運行該鏡像
docker run -d -p 80:80 my_nginx

5. 查看宿主機的80端口
http://192.168.15.80/
# 辣辣小姐姐

Dokcerfile 相關指令用法

COPY

copy指令從宿主機複製文件或者目錄到新的一層鏡像內
如:
copy nana.py /opt

支持多個文件,以及通配符形式的複製,語法要滿足Golang的filepath.Match
copy na* /tmp/cc?.txt /opt

COPY指令能夠保留源文件的元數據,訪問時間等等,這點很重要

ADD

特性和COPY基本一致,不過多了些功能
1. 源文件是一個URL,此時dockcer引擎會下載該鏈接,放入目標路徑,且權限自動設爲600。若這不是期望結果,還得增加一層RUN指令進行調整
# ADD nana.tgz /home
# RUN xxx修改命令
2. 源文件是一個URL,且是一個壓縮包,不會自動解壓,也得單獨用RUN指令解壓
3. 源文件是一個壓縮文件,且是gzip,bzip,xz,tar情況,ADD指令會自動解壓壓縮該文件到沒有文件

CMD

用法,注意是雙引號
# CMD在容器內運行某個命令,啓動程序
# 該鏡像在運行容器實例的時候,執行的具體參數是什麼
CMD["參數1","參數2"]
在指定了entrypoint指令後,用CMD指定具體的參數

dokcer不是虛擬機,容器就是一個進程,既然是進程,那麼程序在啓動的時候需要指定些運行參數,這就是CMD指令作用

例如centos鏡像默認的CMD是/bin/bash,直接docker run -it centos會直接進入bash解釋器。
也可以啓動容器時候,指定參數: docker run -it centos cat /etc/os-release

CMD ["/bin/bash"]

# 該容器運行時,執行的命令
# 等同於命令行的直接操作:docker run -it centos cat /etc/os-release
CMD ["cat","/etc/os-release"]

容器內運行程序

這裏要注意的是,docker 不是虛擬機的概念,虛擬機的程序運行,基本上都是在後臺運行,利用 systemctl 運行,但是容器內沒有後臺進程的概念,必須在前臺運行。容器就是爲了主進程而存在的,主進程如果退出了,容器也就失去意義,自動退出。

例如一個經典的問題:
# 這樣的寫法是錯誤的,容器會立即退出
CMD systemctl start nginx

因爲systemctl start nginx是以守護進程(默認在後臺運行)的形式啓動nginx,且CMD命令會轉化爲

CMD ["sh","-c","systemctl start nginx" ]
這樣的命令主進程是sh解釋器,執行完畢後立即結束了,因此容器也就退出了。

# 相當於nginx -g daemon off
因此正確的做法應該是 CMD ["nginx","-g","daemon off;"]
把宿主機安裝,啓動nginx的理念放入到dockerfile中
1. RUN yum install nginx
2. RUN 配置文件修改 sed
# RUN systemctl start nginx   容器內的程序必須在前臺運行,容器時啓動不了的
3. 正確的寫法應該時CMD ["nginx","-g","daemon off;"]

ENTRYPOINT

**dokcer 面試題:**ENTRYPOINT 和 CMD 的區別以及用法! ! !

ENTRYPOINT作用和CMD一樣,都是在指定容器啓動程序以及參數。

當指定了ENTRYPOINT之後,CMD指令的語義就有了變化,而是把CMD的內容當作參數傳遞給ENTRYPOINT指令。

ENTRYPOINT 和 CMD 的實際用法

實際用法:
1. 準備一個Dokcerfile
[root@docker01 ~]# cd /learn_docker/
[root@docker01 learn_docker]# > Dockerfile 
[root@docker01 learn_docker]# vim Dockerfile 
FROM centos:7.8.2003
RUN rpm --rebuilddb && yum install epel-release -y
RUN rpm --rebuilddb && yum install curl -y
CMD ["curl","-s","ip.sb"]

# 用法如下
dokcer run my_centos curl -s ip.sb		# curl -s ip.sb獲取本機的公網ip地址

2. 構建鏡像
[root@docker01 learn_docker]# docker build .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos:7.8.2003
 ---> afb6fca791e0
Step 2/4 : RUN rpm --rebuilddb && yum install epel-release -y
 ---> Using cache
 ---> 81b4e83fb0a5
Step 3/4 : RUN rpm --rebuilddb && yum install curl -y
 ---> Using cache
 ---> bd0074c78b6c
Step 4/4 : CMD ["curl","-s","ip.sb"]
 ---> Running in 295418f71093
Removing intermediate container 295418f71093
 ---> c920b743282a
Successfully built c920b743282a

3. 查看結果(出現Successfully代表鏡像構建完成)
Step 4/4 : CMD ["curl","-s","ip.sb"]
 ---> Running in 295418f71093
Removing intermediate container 295418f71093
 ---> c920b743282a
Successfully built c920b743282a

4. 檢查鏡像
[root@docker01 learn_docker]# docker tag c920b743282a centos_curl 
[root@docker01 learn_docker]# docker images | grep curl
centos_curl   latest     c920b743282a   3 minutes ago    471MB

5. 運行鏡像,生成容器記錄,沒有前臺運行,因此立即掛了
[root@docker01 learn_docker]# docker run centos_curl
139.227.102.189

6. 上述運行正確,但是我想再傳入一個參數,該怎麼辦
# 發現是無法直接傳入參數的,該形式是覆蓋鏡像中的cmd
# 就好比把docker鏡像,當作一個環境,去執行後面的命令
[root@docker01 learn_docker]# docker run centos_curl pwd
/
[root@docker01 learn_docker]# 
[root@docker01 learn_docker]# docker run centos_curl -I
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "-I": executable file not found in $PATH: unknown.

7. 想要正確的給容器傳入一個參數該怎麼辦
希望容器內能夠正確完整的運作該命令的執行結果
[root@docker01 learn_docker]# curl -s ip.sb -I			# 獲取http報頭信息
HTTP/1.1 200 OK
Date: Wed, 28 Jul 2021 15:16:18 GMT
...

8. 解決辦法
方式一:給容器傳入新的完整的命令,讓後面的命令覆蓋鏡像中的cmd
# 這是投機取巧的辦法,不合適
[root@docker01 learn_docker]# docker run centos_curl curl -s ip.sb -I
HTTP/1.1 200 OK
Date: Wed, 28 Jul 2021 15:18:05 GMT
Content-Type: text/plain

9. 正確的解決辦法
[root@docker01 learn_docker]# vim Dockerfile 
FROM centos:7.8.2003
RUN rpm --rebuilddb && yum install epel-release -y
RUN rpm --rebuilddb && yum install curl -y
ENTRYPOINT ["curl","-s","ip.sb"]

10. 重新構建鏡像
# 重新構建鏡像速度特別快,並且我們發現鏡像的前三個Step的IMAGE ID是一致的,說明前三個的IMAGE ID是直接從緩存中拿的。
# 只有Step 4/4的IMAGE ID發生了變化(Dockerfile文件的第四步是更改過的,是重新構建的鏡像層),因此更加驗證了我們之前所提到的鏡像是分層構建的。
[root@docker01 learn_docker]# docker build .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos:7.8.2003
 ---> afb6fca791e0
Step 2/4 : RUN rpm --rebuilddb && yum install epel-release -y
 ---> Using cache
 ---> 81b4e83fb0a5
Step 3/4 : RUN rpm --rebuilddb && yum install curl -y
 ---> Using cache
 ---> bd0074c78b6c
Step 4/4 : ENTRYPOINT ["curl","-s","ip.sb"]
 ---> Running in df106e04d533
Removing intermediate container df106e04d533
 ---> e9479067148c
Successfully built e9479067148c

11. 重新運行該鏡像,看結果,以及傳入新的參數
[root@docker01 learn_docker]# docker tag e9479067148c centos_curl_new

# 此時發現,傳入的CMD指令,當作了ENTRYPOINT的參數
# 其實容器內,執行的完命令是: curl -s ip.sb -I
[root@docker01 learn_docker]# docker run centos_curl_new -I
HTTP/1.1 200 OK
Date: Wed, 28 Jul 2021 15:24:58 GMT
...

ARG 和 ENV 指令

設置環境變量

dockerfile腳本,shell腳本

ENV 
ENV AGE=18
ENV MYSQL_VERSION=5.6

後續所有的操作,通過$NAMME就可以直接獲取變量值使用了,維護dockerfile更加方便

ARG和ENV一樣,都是設置環境變量
ENV無論是在鏡像構建時,還是容器運行,該變量都可以使用
ARG只是用於構建鏡像需要設置的變量,容器運行時就消失了

VOLUME

容器在運行時,應該保證在存儲層不寫入任何數據,運行在容器內產生的數據,我們推薦是掛載,寫入到宿主機上,進行維護。

# mount /mnt 

VOLUME /data  
# 將容器內的/data文件夾,在容器運行時,該目錄自動掛載爲匿名卷,任何向該目錄中寫入數據的操作,都不會被容器記錄,保證的容器存儲無狀態理念。

# Dockerfile
[root@docker01 ~]# cd /learn_docker/
[root@docker01 learn_docker]# > Dockerfile 
[root@docker01 learn_docker]# vim Dockerfile 
FROM centos
MAINTAINER nana
VOLUME ["/data1","/data2"]

# 該容器運行的時候,這兩個目錄自動和宿主機的目錄做好映射關係
docker build .

# 運行該鏡像
docker run 86b4dceba89a
# 查看生成的容器信息
[root@docker01 nana]# docker ps -a | head -2
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS                     PORTS     NAMES
84014622b3a4   86b4dceba89a      "/bin/bash"              2 minutes ago   Exited (0) 2 minutes ago             sharp_noether

# dokcer inspect命令查看
[root@docker01 learn_docker]# docker inspect 86b4dceba89a
            "Volumes": {
                "/data1": {},
                "/data2": {}
            },

1. 容器數據掛載的方式,通過dockerfile,指定VOLUME目錄
2. 通過docker run -v參數,直接設置需要映射掛載的目錄

EXPOSE

指定容器運行時對外提供的端口服務。

幫助使用該鏡像的人,快速理解該容器的一個端口業務
docker port 容器
dokcer run -p 宿主機端口:容器端口
docker run -P # 作用是隨機 宿主機端口:容器內端口

WORKDIR

用於在 dockerfile 中,目錄的切換,更改工作目錄

WORKDIR /opt

USER

用於改變環境,用於切換用戶

USER root
USER nana

使用 Dockerfile 構建一個網站鏡像

傳統方式創建一個網站站點

  1. nginx,修改首頁內容,html 網站就跑起來了。web server,提供 web 服務,提供代理轉發,提供網關,限流等等。。。

  2. web framework。web 框架,一般由開發,通過某個開發語言,基於某個 web 框架,自己去開發一個 web 站點,python,django 框架。

使用 Dockerfile 創建一個網站站點

  1. 用 python 語言,基於 flask web 框架,開發一個自己的網站,寫一個後端的網站代碼

  2. 開發 dockerfile,部署該代碼,生成鏡像

  3. 其他人基於該鏡像,docker run 就可以在電腦跑起來你這個網站

使用 docker 的優勢

  • 比如安裝一個 etcd、naco,都是一些比較複雜的軟件。

  • 需要依賴於 go 語言環境,比如需要依賴於 java 環境,在自己的機器安裝好對應的開發環境,以及對應的版本,以及各種依賴。。。

    tomcat  	依賴於jdk環境
        		
    當你有了docker,
    docker pull tomcat  		# 這些主流的鏡像都可以直接找到,並且該鏡像中,就已經打包好了java環境
    docker run tomcat xxx ... 		# 直接可以訪問tomcat了
1. 在宿主機下,準備一個目錄,準備好dockerfile,代碼文件
# 寫一個flask的python代碼
# 創建代碼文件
[root@docker01 ~]# cd /learn_docker/
[root@docker01 ~]# vim nana_flask.py 
#coding:utf8
from flask import Flask
app=Flask(__name__)
# @app.route(裝飾器),網站的route,指的是url地址後面的文件路徑
@app.route("/nana")
def nana():
    return "From Docker,nana是隻臭豬豬!!!"
if __name__=="__main__":
    app.run(host="0.0.0.0",port=8080)

2. 編寫Dockerfile
[root@docker01 learn_docker]# vim Dockerfile
FROM centos:7.8.2003
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo;
RUN curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo;
RUN yum makecache fast;
RUN yum install python3-devel python3-pip -y
RUN pip3 install -i https://pypi.douban.com/simple flask
COPY nana_flask.py /opt
WORKDIR /opt
EXPOSE 8080
CMD ["python3","nana_flask.py"]

3. 構建鏡像
# --no-cache不是使用之前舊的緩存,重新構建鏡像
[root@docker01 learn_docker]# docker build --no-cache -t "nana_flask" .
...
Successfully built 9e731f439e41
Successfully tagged nana_flask:latest

4. 查看構建好的鏡像
[root@docker01 learn_docker]# docker images | head -2
REPOSITORY        TAG        IMAGE ID       CREATED          SIZE
nana_flask        latest     9e731f439e41   3 minutes ago    649MB

5. 運行鏡像,生成容器
[root@docker01 learn_docker]# docker run -d --name nana_flask_web01 -p 90:8080 nana_flask
d9f2f83d16bdd0364473d6e4043c433cbd8e3286e87ecbf93fb3fd5e08ac8002

[root@docker01 learn_docker]# docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS         PORTS                                   NAMES
d9f2f83d16bd   nana_flask   "python3 nana_flask.…"   2 minutes ago   Up 2 minutes   0.0.0.0:90->8080/tcp, :::90->8080/tcp   nana_flask_web01

6. 訪問宿主主機,查看容器內的flask web網站
瀏覽器輸入: http://192.168.15.80:90/nana
# From Docker,nana是隻臭豬豬!!!

如何修改容器內的網站的內容

方法一:修改宿主機的代碼,以及 dockerfile,重新構建鏡像

[root@docker01 ~]# vim nana_flask.py 
...
def nana():
    return "From Docker,nana是隻臭豬豬!!!"				# 修改return值,重新生成鏡像
...

方法二:你可以經入到已經運行的容器內,修改代碼,重啓容器即可

1. 進入容器內部
[root@docker01 learn_docker]# docker exec -it d9f2f83d16bd bash
[root@d9f2f83d16bd opt]# ls                
nana_flask.py

2. 修改容器內的代碼
[root@d9f2f83d16bd opt]# vi nana_flask.py 
#coding:utf8
from flask import Flask
app=Flask(__name__)
# @app.route(裝飾器),網站的route,指的是url地址後面的文件路徑
@app.route("/nana")
def nana():
    return "From Docker,nana是隻臭豬豬!!!ABC!!!"
if __name__=="__main__":
    app.run(host="0.0.0.0",port=8080)

3. 退出容器並重啓容器
[root@d9f2f83d16bd opt]# exit
exit
[root@docker01 learn_docker]# docker restart d9f2f83d16bd
d9f2f83d16bd
[root@docker01 learn_docker]# docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS              PORTS                                   NAMES
d9f2f83d16bd   nana_flask   "python3 nana_flask.…"   4 minutes ago   Up About a minute   0.0.0.0:90->8080/tcp, :::90->8080/tcp   nana_flask_web01

4. 訪問宿主主機,查看容器內的flask web網站
瀏覽器輸入: http://192.168.15.80:90/nana
# From Docker,nana是隻臭豬豬!!!ABC!!!

Docker 基礎複習

Docker 容器文件系統

容器是 docker 的一個核心概念,容器使用一個或者一組應用,他的運行狀態如下:

  • docker 利用容器運行應用程序

  • 容器是鏡像的運行實例,可以被 run、start、stop、rm

  • 每個容器都是互相隔離,保證平臺暗轉

  • 容器可以看作是一個簡易版 Linux 環境 (沒有 Linux 內核,有 root 權限、進程、用戶空間、網絡)

  • 鏡像是隻讀的,容器在啓動的時候創建一層可寫層 dokcerfile 面向開發,docker image(鏡像) 作爲交付標準,docker container(容器) 涉及部署和運維,三者合起來完成 docker 體系。

    FROM ubuntu:14.04				選擇基礎鏡像
    ADD run.sh					 	添加文件鏡像,這一層鏡像只有一個內容,就是這個文件
    VOLUME /data					設定存儲目錄,並未添加文件,只是更新了鏡像的json文件,便於啓動時候讀取該層信息
    CMD ["./run.sh"] 			    更新json文件,設定程序入口

docker 容器管理總結

# 運行鏡像,且進入容器內
[root@docker01 ~]# docker run -it ubuntu bash
root@7478064e9fff:/# 

# 容器運行web程序
# 注意端口使用,數字大一點,建議8000以後開始使用


# --restart=always容器在後臺掛掉後,默認重啓容器
[root@docker01 ~]# docker run --name my_nginx -d --restart=always -p 8000:80 nginx
79d7fcfdc60f2c40e6d92790be6ad6f3bf9db49fda0e46cadb196be6677b4f73

[root@docker01 ~]# docker ps | head -2
CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS          PORTS                                   NAMES
79d7fcfdc60f   nginx        "/docker-entrypoint.…"   40 seconds ago   Up 39 seconds   0.0.0.0:8000->80/tcp, :::8000->80/tcp   my_nginx

瀏覽器訪問:http://192.168.15.80:8000/ ==> 可以訪問到nginx

# 查看容器內日誌,實時刷新
docker logs -f

# 查看運行時,以及掛掉的容器記錄
docker ps 	在運行的容器
dokcer ps -a 掛掉以及活着的容器

# 停止啓動
docker start
docker stop

# 進入容器內
docker exec -it 容器id bash

# 刪除容器
docker rm 容器id
docker rm `docker ps -qa`
# 強制殺死並刪除容器
docker rm -f 容器id

# 查看容器進程資源信息
docker top 容器id

# 查看容器內資源
docker stats 容器id

# 查看容器具體信息
docker inspect 容器id

# 獲取容器內的ip地址,容器的格式化參數
docker inspect --format '{{ .NetworkSettings.IPAddress }}' 容器id

dokcer run 啓動容器的時候,dokcer 後臺操作流程是

  • 檢查本地是否有該鏡像,沒有就下載

  • 利用鏡像創建且啓動一個容器

  • 分配容器文件系統,在只讀的鏡像層掛載讀寫層

  • 宿主機的網橋接口會分配一個虛擬接口到容器中

  • 容器獲得地址池的 ip 地址

  • 執行用戶指定的程序

  • 若程序裏沒有進程在運行,容器執行完畢後立即終止

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Vpa7CEERUDLWe5EOWL22Sg