Docker的優(yōu)勢
可以將Docker的產(chǎn)生理解為一種相比虛擬機(jī)更加輕量級的虛擬化技術(shù)腹忽,前有Openstack用來在物理機(jī)上面構(gòu)建出物理資源磕昼,以及很早以前就有的Linux NameSpace技術(shù),Docker也就應(yīng)運(yùn)而生瓷产,稱為承載單獨(dú)進(jìn)程/服務(wù)的載體翠拣;
Docker的優(yōu)勢在于使用簡單(最初設(shè)計(jì)的時候就沒有考慮過多的功能)谆沃,可以運(yùn)行在各種環(huán)境上面(物理機(jī)、虛擬機(jī)介袜、個人PC甫何、云主機(jī)),可移植性也很好(統(tǒng)一使用Docker-hub遇伞,或者重載別人的Dockerfile)
如何運(yùn)行一個Docker容器
首先獲取一個Docker鏡像辙喂,一般從鏡像倉庫當(dāng)中拉取(有公網(wǎng)上的各個鏡像倉庫鸠珠,類似于apt巍耗、yum源),拉取的過程當(dāng)中可以看到本地主機(jī)會下載一個鏡像壓縮包并解壓渐排,拉取鏡像成功之后可以使用docker images查看本地的所有鏡像信息炬太,信息主要包括鏡像名,鏡像的tag(一般用于表示同一鏡像的不同版本驯耻,比如ubuntu的版本就通過tag號來體現(xiàn))娄琉,鏡像的ID以及鏡像的大小
然后就可以使用Docker RUN命令來給予選定的鏡像創(chuàng)建容器了次乓,命令格式為
docker run [參數(shù)] [鏡像源] [容器啟動的命令參數(shù)]
其中run后面可以附帶多個參數(shù),比如指定容器的映射端口孽水、容器的網(wǎng)絡(luò)模式票腰、容器的掛載Volume、容器后臺運(yùn)行等等女气,鏡像源就是之前拉取的鏡像名稱:鏡像Tag杏慰,這里需要注意的是如果命令當(dāng)中定義的鏡像源在本地找不到的話,Docker會主動去Docker-hub上面尋找鏡像炼鞠,最后是容器啟動的時候的命令參數(shù)缘滥,這里可以運(yùn)行一條或者多條命令,比如最簡單的場景谒主,我們寫一個/bin/bash朝扼,代表運(yùn)行容器的時候一并運(yùn)行一個bash shell終端,這里其實(shí)也可以運(yùn)行多條命令霎肯,比如我們要在指定的目錄下面運(yùn)行某一個shell腳本擎颖,可以附帶這個命令 /bin/bash -c "命令1&&命令2&&命令3"
運(yùn)行完成容器之后,我們可以使用docker ps命令來查看當(dāng)前正在運(yùn)行的容器观游,可以看到docker同樣會為每一個容器都分配一個ID作為標(biāo)識(其實(shí)這里只是短ID)搂捧,同時會顯示運(yùn)行時間和端口號等信息;
以這里ubuntu容器為例懂缕,我們可以使用docker attach命令進(jìn)入到容器當(dāng)中允跑,進(jìn)去之后可以看容器內(nèi)部的目錄結(jié)構(gòu)和Ubuntu系統(tǒng)的目錄結(jié)構(gòu)幾乎一模一樣,但是查看一下當(dāng)前系統(tǒng)的進(jìn)程搪柑,發(fā)現(xiàn)當(dāng)前容器內(nèi)部僅有一個/bin/bash進(jìn)程在運(yùn)行(而這個進(jìn)程正是我們在docker run命令里面指定的)聋丝,再進(jìn)一步進(jìn)入/proc目錄下面查看當(dāng)前系統(tǒng)的配置,信息都是和docker宿主機(jī)的信息一模一樣工碾,我推測這些文件壓根就是宿主機(jī)的文件(這部分和宿主機(jī)共用了相同的Namespace)弱睦,由此可見Docker容器是為了容器內(nèi)運(yùn)行的進(jìn)程服務(wù)的,只為進(jìn)程提供可以正常運(yùn)行的各種必要條件倚喂,而不像通常主機(jī)的系統(tǒng)需要為成百上千個進(jìn)程服務(wù)
如何構(gòu)建自定義的Docker鏡像
一般有兩種方法來構(gòu)建自定義的Docker鏡像
- 運(yùn)行一個基礎(chǔ)的通用系統(tǒng)鏡像(docker-hub上面的ubuntu每篷、centos等系統(tǒng)鏡像),然后創(chuàng)建并進(jìn)入容器端圈,在容器當(dāng)中按照平時在系統(tǒng)上面的配置來完成自己想要的配置焦读、服務(wù)部署,最后使用docker commit命令將容器保存在鏡像
- 使用Dockerfile來構(gòu)建鏡像(推薦方式)舱权,Dockerfile相當(dāng)于一個Docker能夠讀懂的描述文件矗晃,里面會指定Docker的基礎(chǔ)鏡像,以及在基礎(chǔ)鏡像之上做的各種操作(文件拷貝宴倍,容器內(nèi)部命令執(zhí)行张症,容器啟動時附帶的命令)仓技,重要的是Dockerfile可讀性比較好,你的Dockerfile可以清晰的讓別人知道你在構(gòu)建鏡像的過程當(dāng)中做了哪些動作俗他,另外一個方面脖捻,使用Dockerfile方式構(gòu)建的鏡像實(shí)際是一個多層的鏡像(在最初的基礎(chǔ)鏡像之上每執(zhí)行一條Dockerfile的命令都相當(dāng)于是給這個鏡像套了一層殼),別人甚至可以通過修改我們的鏡像文件來方便進(jìn)行修改(比如把某一層的操作置空)
Dockerfile創(chuàng)建鏡像的具體步驟
首先需要創(chuàng)建一個dockerfile的目錄兆衅,目錄內(nèi)部創(chuàng)建一個名為Dockerfile的文件地沮,同時可以放置一些后續(xù)將會拷貝到容器當(dāng)中的文件,創(chuàng)建好Dockerfile之后使用docker build命令來生成我們的自定義鏡像羡亩。具體的一些Dockerfile重要命令如下:
- FROM:定義自定義鏡像使用的基礎(chǔ)鏡像摩疑,這里通常為鏡像倉庫里面的通用系統(tǒng)鏡像
- ENV:在Dockerfile內(nèi)聲明一個變量,供后面的命令使用
- ADD:從宿主機(jī)上面拷貝文件到容器內(nèi)的指定目錄下
- COPY:作用與ADD類似畏铆,也是拷貝文件到容器雷袋,但是ADD的功能更加強(qiáng)大一些,比如ADD可以在拷貝的過程當(dāng)中對壓縮文件進(jìn)行解壓辞居,同時ADD支持從URL獲取文件(但是這里有坑楷怒,就是如果目標(biāo)URL需要認(rèn)證的話,ADD命令是無法附帶認(rèn)證信息的)
- RUN:在容器當(dāng)中運(yùn)行一條命令速侈,任何命令都可以率寡,但是命令出錯的話會導(dǎo)致docker build過程中斷(比如RUN一條apt-get intall命令中間有輸入Y的操作迫卢,這個時候就需要使用管道命令了倚搬,可以自動輸入Y、Yes等選擇)
- VOLUME:為容器添加一個掛載卷(在容器創(chuàng)建的時候Docker會默認(rèn)在宿主機(jī)/lib/docker/container下面創(chuàng)建一個目錄作為Docker的掛載卷)乾蛤,用戶也可以手動定義掛載卷每界,比如多個容器掛載同一個Volume實(shí)現(xiàn)數(shù)據(jù)共享,比如自定義一個目錄作為Volume來保存容器運(yùn)行時候生成的日志家卖,Volume在容器停止眨层、刪除之后并不會被清除,會一直存在(當(dāng)然這應(yīng)該還是有些坑的上荡,比如Docker運(yùn)行長時間之后會產(chǎn)生很多的垃圾Volume趴樱,占用宿主機(jī)的磁盤空間)
- EXPOSE: 這個命令其實(shí)只是在dockerfile當(dāng)中指示容器將會暴露的端口號,具體暴露指定端口號的操作需要在docker run的時候使用-p指定或者-P由宿主機(jī)分配動態(tài)端口
- CMD以及ENTRYPOINT:這兩個命令放在一起寫一下酪捡,之前我一直都只用過CMD沒用過ENTRYPOINT叁征,然而看了看Docker文檔中的定義,這兩個命令都是有明確的使用場景定義的
The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.
The CMD instruction has three forms:
CMD ["executable", "param1", "param2"](exec form, this is the preferred form)
CMD ["param1","param2"](as default parameters to ENTRYPOINT)
CMD command param1 param2(shell form)
可以看出逛薇,CMD是作為一個默認(rèn)的執(zhí)行參數(shù)來配合容器運(yùn)行的捺疼,CMD可以定義一條獨(dú)立的參數(shù)(帶中括號的標(biāo)準(zhǔn)形式以及不帶中括號的默認(rèn)shell執(zhí)行模式),也可以只定義參數(shù)輔助ENTRYPOINT執(zhí)行永罚;而ENTRYPOINT的語法與CMD類似啤呼,只是ENTRYPOINT可以只定義executable命令卧秘,參數(shù)可以由CMD補(bǔ)充或者再docker run的時候添加,同時ENTRYPOINT也不會簡單被docker run后面跟的命令覆蓋掉(可以手動添加--entrypoint參數(shù)來覆蓋dockerfile當(dāng)中的entrypoint)
Docker的網(wǎng)絡(luò)原理
這里只簡單寫一點(diǎn)原生的Docker網(wǎng)絡(luò)原理官扣,不去考慮開源網(wǎng)絡(luò)組件或是k8s的網(wǎng)絡(luò)實(shí)現(xiàn)翅敌,首先來看一看Docker依托的幾個Linux網(wǎng)絡(luò)原理:
- 網(wǎng)絡(luò)命名空間: 網(wǎng)絡(luò)命名空間也是命名空間的一種,具體實(shí)現(xiàn)原理相當(dāng)于是在創(chuàng)建網(wǎng)絡(luò)設(shè)備的時候添加私有全局變量(變量以命名空間的名稱命名)惕蹄,實(shí)現(xiàn)網(wǎng)絡(luò)設(shè)備綁定哼御,也實(shí)現(xiàn)了不同網(wǎng)絡(luò)命名空間之間的隔離(因?yàn)槭瞻l(fā)流量的網(wǎng)絡(luò)設(shè)備都不一樣了)
- veth: veth為一對虛擬網(wǎng)卡,前面說了Docker使用不同的網(wǎng)絡(luò)命名空間來實(shí)現(xiàn)網(wǎng)絡(luò)上的隔離焊唬,那么veth就可以作為不同網(wǎng)絡(luò)空間鏈接的通道恋昼,一對veth的特性是在其中一個veth上進(jìn)行數(shù)據(jù)收發(fā),另外一個veth上面也能有相同的數(shù)據(jù)收發(fā)
- netfilter和iptables: netfilter定義了一些鉤子函數(shù)赶促,可以讓Linux在處理數(shù)據(jù)包的各個階段做一些用戶自定義的動作液肌,鉤子函數(shù)的掛載點(diǎn)包括INPUT绞灼、PREROUTE昏苏、FORWARD、AFTERROUTE闪彼、OUTPUT五個時間點(diǎn)婿滓;iptables定義了一系列的ip包處理規(guī)則表老速,規(guī)則表有RAW、MANGLE凸主、NAT橘券、FILTER四種類型,優(yōu)先級依次降低卿吐,具體的規(guī)則表定義了掛載點(diǎn)旁舰、表類型、匹配參數(shù)嗡官、匹配動作這些信息
- 網(wǎng)橋: 網(wǎng)橋可以實(shí)現(xiàn)2層網(wǎng)絡(luò)的交換箭窜,通過學(xué)習(xí)不同端口的MAC地址,在接收到數(shù)據(jù)包的時候根據(jù)目的地址進(jìn)行轉(zhuǎn)發(fā)
在實(shí)際Docker應(yīng)用中衍腥,Docker需要解決的場景包括:
- 容器與容器的網(wǎng)絡(luò)
- 容器到外部的網(wǎng)絡(luò)
- 外部到容器的網(wǎng)絡(luò)
而Docker容器的網(wǎng)絡(luò)模式有4種:
- bridge模式:表示容器使用網(wǎng)橋模式磺樱,這也是docker默認(rèn)的容器網(wǎng)絡(luò)模式,網(wǎng)橋模式會在創(chuàng)建容器的時候在容器內(nèi)部和docker0網(wǎng)橋上面創(chuàng)建一對veth婆咸,容器之間通過docker0網(wǎng)橋中轉(zhuǎn)進(jìn)行收發(fā)數(shù)據(jù)包
- host模式:為容器分配實(shí)際的ip地址竹捉,容器與宿主機(jī)之間,設(shè)置與外部網(wǎng)絡(luò)之間直接進(jìn)行網(wǎng)絡(luò)交互(前提是添加好收發(fā)的路由)
- container模式: 創(chuàng)建容器的時候指定使用另外一個容器的網(wǎng)絡(luò)(比如k8s當(dāng)中pod內(nèi)的容器都是共享了pause容器的網(wǎng)絡(luò))
- none模式: 支持用戶高度自定義的網(wǎng)絡(luò)模式
以默認(rèn)的網(wǎng)橋模式為例擅耽,簡單分析一下網(wǎng)絡(luò)實(shí)現(xiàn)的細(xì)節(jié)活孩,在安裝好docker-engine之后,我們可以看到系統(tǒng)除loopback,eth0之外憾儒,還出現(xiàn)了一個docker0設(shè)備(擁有一個172.17開頭的16位地址段询兴,docker0本身使用了第一個地址)
root@xiaohu-MS-7B23:/home/xiaohu# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:d8:61:0c:aa:5d brd ff:ff:ff:ff:ff:ff
inet 192.168.31.242/24 brd 192.168.31.255 scope global dynamic noprefixroute eno1
valid_lft 42712sec preferred_lft 42712sec
inet6 fe80::b507:cfa1:faad:98d6/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:c1:ad:63:12 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
再看一下當(dāng)前的iptable,在NAT表當(dāng)中起趾,第一條表示在進(jìn)行路由表匹配之前诗舰,將所有目的地址為本機(jī)地址的數(shù)據(jù)包都送到DOCKER規(guī)則的表當(dāng)中進(jìn)行匹配,第三條表示在路由匹配完成之后训裆,將源地址為容器眶根,目的地址不為docker0的數(shù)據(jù)包都經(jīng)過地址轉(zhuǎn)換(MASQUERADE,將容器地址轉(zhuǎn)換為宿主機(jī)的地址)
root@xiaohu-MS-7B23:/home/xiaohu# iptables-save
# Generated by iptables-save v1.6.1 on Mon Feb 25 23:37:34 2019
*nat
:PREROUTING ACCEPT [5:463]
:INPUT ACCEPT [5:463]
:OUTPUT ACCEPT [268:17428]
:POSTROUTING ACCEPT [268:17428]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Mon Feb 25 23:37:34 2019
# Generated by iptables-save v1.6.1 on Mon Feb 25 23:37:34 2019
*filter
:INPUT ACCEPT [2769:3426683]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [2760:295032]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Mon Feb 25 23:37:34 2019
此時我們創(chuàng)建一個容器边琉,容器使用了動態(tài)端口映射属百,然后再來看一看iptables,在NAT的表當(dāng)中变姨,增加了兩條規(guī)則族扰,第四條有點(diǎn)看不懂,猜測可能是docker新版本做的優(yōu)化(目的和源地址一模一樣還需要作NAT
轉(zhuǎn)換嗎定欧,會不會是防止宿主機(jī)之間相同地址段沖突呢)渔呵,第六條表示經(jīng)過第一條和第二條規(guī)則匹配到的目的地址為宿主機(jī)本地地址的包,如果目的端口為動態(tài)分配的端口(此處為32768)砍鸠,則轉(zhuǎn)換為容器的地址以及暴露端口(22端口)扩氢,這樣外部的主機(jī)就可以通過動態(tài)映射端口來訪問我們的容器了。
root@xiaohu-MS-7B23:/home/xiaohu# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9efb0e0685ba ubuntu:latest "/bin/bash" 4 seconds ago Up 2 seconds 0.0.0.0:32768->22/tcp condescending_shtern
root@xiaohu-MS-7B23:/home/xiaohu#
root@xiaohu-MS-7B23:/home/xiaohu#
root@xiaohu-MS-7B23:/home/xiaohu# iptables-save
# Generated by iptables-save v1.6.1 on Tue Feb 26 00:39:21 2019
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [17:1311]
:POSTROUTING ACCEPT [17:1311]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 22 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 32768 -j DNAT --to-destination 172.17.0.2:22
COMMIT
# Completed on Tue Feb 26 00:39:21 2019
# Generated by iptables-save v1.6.1 on Tue Feb 26 00:39:21 2019
*filter
:INPUT ACCEPT [50:5673]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [54:5312]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Tue Feb 26 00:39:21 2019
那么容器訪問外部是如何實(shí)現(xiàn)的呢爷辱,在默認(rèn)的bridge模式下录豺,容器的包走向外部都是需要通過docker0網(wǎng)橋中轉(zhuǎn)的,一旦包到了docker0之后托嚣,由于docker0是屬于系統(tǒng)的網(wǎng)絡(luò)namespace的巩检,所以能夠去匹配系統(tǒng)的路由表厚骗,在路由表當(dāng)中明確指示了要把數(shù)據(jù)包發(fā)往外部需要的默認(rèn)路由示启,匹配完路由表之后,再去執(zhí)行iptables當(dāng)中的OUTPUT與POSTROUTE動作领舰,在這里就是-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
這一條規(guī)則了夫嗓,說明了發(fā)往外部的包需要進(jìn)行NAT地址轉(zhuǎn)換,將源地址由容器地址轉(zhuǎn)換為宿主機(jī)的網(wǎng)卡地址冲秽,這樣就完成了容器到外部的發(fā)包
root@xiaohu-MS-7B23:/home/xiaohu# netstat -rn
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 192.168.31.1 0.0.0.0 UG 0 0 0 eno1
169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eno1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.31.0 0.0.0.0 255.255.255.0 U 0 0 0 eno1
以上就是當(dāng)前我所了解的一些Docker網(wǎng)絡(luò)知識了舍咖,從上面這幾個原理實(shí)現(xiàn)我們可以看出,Docker基本對于異主機(jī)之間的容器通信是沒有支持的锉桑,每一臺主機(jī)上面都默認(rèn)規(guī)劃了同一地址段來供容器使用排霉,后面學(xué)習(xí)k8s的過程當(dāng)中再去對異主機(jī)的容器通信進(jìn)行深入學(xué)習(xí),比如使用flannel組建在創(chuàng)建容器之時就針對每個宿主機(jī)劃分不同的地址段避免沖突并添加路由民轴,使用OVS構(gòu)建宿主機(jī)之間的容器隧道等方案