Docker
容器是一種沙盒技術(shù)确徙。沙盒就是能夠像一個(gè)集裝箱一樣把應(yīng)用(進(jìn)程)裝起來。這樣應(yīng)用(進(jìn)程)與應(yīng)用(進(jìn)程)之間因?yàn)橛辛诉吔缍ゲ桓蓴_兆旬,被裝進(jìn)集裝箱的一個(gè)用也可以方便的搬來搬去结胀。
- 邊界->運(yùn)行態(tài)->container
- 打包->靜態(tài)->image
image與container是1:n的關(guān)系。一個(gè)image可以生成多個(gè)container饵隙。就是 類與對(duì)象的概念。
運(yùn)行態(tài)
容器的本質(zhì)是進(jìn)程
容器的本質(zhì)是進(jìn)程
容器的本質(zhì)是進(jìn)程
進(jìn)程
進(jìn)程(Process)是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng)沮脖,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位金矛,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在早期面向進(jìn)程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中勺届,進(jìn)程是程序的基本執(zhí)行實(shí)體驶俊;在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器免姿。程序是指令饼酿、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體胚膊。
程序運(yùn)行起來之后計(jì)算機(jī)執(zhí)行環(huán)境(比如內(nèi)存中的數(shù)據(jù)故俐、寄存器里的值、堆棧種的指令紊婉、打開的文件药版、以及各種設(shè)備的狀態(tài)信息的一個(gè)集合)的總和叫做進(jìn)程
線程(英語:thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中喻犁,是進(jìn)程中的實(shí)際運(yùn)作單位槽片。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流何缓,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)筐乳。
同一進(jìn)程中的多條線程將共享該進(jìn)程中的全部系統(tǒng)資源歌殃,如虛擬地址空間,文件描述符和信號(hào)處理等等蝙云。但同一進(jìn)程中的多個(gè)線程有各自的調(diào)用棧(call stack)氓皱,自己的寄存器環(huán)境(register context),自己的線程本地存儲(chǔ)(thread-local storage)勃刨。
對(duì)于進(jìn)程而言波材,它的靜態(tài)表現(xiàn)就是程序,一個(gè)二進(jìn)制包或者一堆庫加可執(zhí)行文件身隐。而一旦運(yùn)行起來廷区,就變成了計(jì)算機(jī)里的數(shù)據(jù)和狀態(tài)的總和,這就是它的動(dòng)態(tài)表現(xiàn)贾铝。
容器技術(shù)的核心功能就是通過約束和修改進(jìn)程的動(dòng)態(tài)表現(xiàn)隙轻,從而為其創(chuàng)造出一個(gè)邊界
通常來說是個(gè)通過Cgroups技術(shù)來約束(限制Memory、CPU等)垢揩,而通過Namespace技術(shù)來修改進(jìn)程視圖(網(wǎng)絡(luò)玖绿、用戶等)。
Namespace
# 查看所有namespace
# Cgroup namespace 目前還沒有被 docker 采用叁巨。
ll /proc/1/ns/
- Mount Namespace -> 掛載
- UTS Namespace -> HostName
- IPC Namespace -> 進(jìn)程間通信
- PID Namespace -> 進(jìn)程號(hào)
- Network Namespace -> 網(wǎng)絡(luò)
- User Namespace -> 用戶
進(jìn)入某一個(gè)進(jìn)程的namespace可以用nsenter 命令
$ nsenter --help
Usage:
nsenter [options] [<program> [<argument>...]]
Run a program with namespaces of other processes.
Options:
-a, --all enter all namespaces
-t, --target <pid> target process to get namespaces from
-m, --mount[=<file>] enter mount namespace
-u, --uts[=<file>] enter UTS namespace (hostname etc)
-i, --ipc[=<file>] enter System V IPC namespace
-n, --net[=<file>] enter network namespace
-p, --pid[=<file>] enter pid namespace
-C, --cgroup[=<file>] enter cgroup namespace
-U, --user[=<file>] enter user namespace
-S, --setuid <uid> set uid in entered namespace
-G, --setgid <gid> set gid in entered namespace
--preserve-credentials do not touch uids or gids
-r, --root[=<dir>] set the root directory
-w, --wd[=<dir>] set the working directory
-F, --no-fork do not fork before exec'ing <program>
-Z, --follow-context set SELinux context according to --target PID
-h, --help display this help
-V, --version display version
For more details see nsenter(1).
PID Namespace是用來隔離進(jìn)程ID的 同樣一個(gè)進(jìn)程在不同的PID Namespace里可以擁有不同的PID斑匪。可以看到的一個(gè)現(xiàn)象是锋勺。在docker container中 ps -ef
時(shí)蚀瘸,前臺(tái)的進(jìn)程PID是1.但是在容器外,該進(jìn)程有不同的PID
UTC Namespace 主要用來隔離nodename和domainname兩個(gè)系統(tǒng)標(biāo)識(shí)
hostname
IPC Namespace用來隔離System V IPC(信號(hào)量庶橱、消息隊(duì)列贮勃、共享內(nèi)存)和 POSIX message queues
#查詢 message queue
ipcs -q
# 創(chuàng)建 message queue
ipcmk -Q
Mount Namespace 用來隔離各個(gè)進(jìn)程看到的掛載點(diǎn)視圖在不同Namespace的進(jìn)程中,看到的文件系統(tǒng)層次是不一樣的苏章。在Mount Namespace中調(diào)用mount和unmount僅僅只會(huì)影響當(dāng)前Namespace的文件系統(tǒng)衙猪。而對(duì)全局的文件系統(tǒng)沒有影響。
df -h
User Namespace 主要是隔離用戶的用戶組ID 也就是說布近,一個(gè)進(jìn)程的User ID和Group ID在User Namespace內(nèi)外是可以不同的。
cat /etc/passwd
# 用postgresql鏡像切換用戶 到 psostgre
top
# 在宿主機(jī)
ps aux|grep top
掛載的文件在容器內(nèi)外顯示的屬組和屬主不同不是因?yàn)檫@個(gè)丝格,是因?yàn)槲募鎯?chǔ)信息中保存的是userID撑瞧。在容器內(nèi)外的userID對(duì)應(yīng)的用戶名是不一樣的。
Network Namespace 是用來隔離網(wǎng)絡(luò)設(shè)備显蝌、IP地址端口等網(wǎng)絡(luò)棧的
ip addr
Cgroups
Linux Cgroups提供了對(duì)一組進(jìn)程以及將來子進(jìn)程的資源限制预伺、控制和統(tǒng)計(jì)的能力订咸。這些資源包括CPU、內(nèi)存酬诀、存儲(chǔ)脏嚷、網(wǎng)絡(luò)等。通過Cgroups瞒御,可以方便地限制某個(gè)進(jìn)程的資源占用父叙。
- cgroup 對(duì)進(jìn)程分組管理的一種機(jī)制,一個(gè)cgroup包含一組進(jìn)程肴裙。并且可以在這個(gè)cgroup上增加subsystem的各種參數(shù)配置趾唱。
- subsystem是一組資源控制的模塊。
lssubsys -a
- hierarchy的功能是把一組cgroup串成一個(gè)樹狀結(jié)構(gòu)蜻懦。
手動(dòng)配置Cgroups:
# 首先創(chuàng)建并掛載一個(gè)hierarchy(cgroup樹).
$ mkdir cgroup-test
$ mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test
$ ls ./cgroup-test/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
- cgroup.clone_children cpuset的subsystem會(huì)讀取這個(gè)配置文件甜癞,如果這個(gè)值是1(默認(rèn)是0)子cgroup才會(huì)繼承父cgroup的cpuset的配置
- cgroup.procs是樹中當(dāng)前節(jié)點(diǎn)cgroup中的進(jìn)程組ID,現(xiàn)在的位置是在根節(jié)點(diǎn)宛乃,這個(gè)文件中會(huì)有現(xiàn)在系統(tǒng)中所有進(jìn)程組的ID
- notify_on_release和release_agent會(huì)一起使用悠咱,notify_on_release表示當(dāng)這個(gè)cgroup最后一個(gè)進(jìn)程退出的時(shí)候是否執(zhí)行了release_agent; release_agent 則是一個(gè)路徑,通常用作進(jìn)程退出后自動(dòng)清理不再使用的cgroup征炼。
- tasks 標(biāo)識(shí)該cgroup下面的進(jìn)程ID析既,如果把一個(gè)進(jìn)程ID寫到tasks文件中,便會(huì)將相應(yīng)的進(jìn)程加入到這個(gè)cgroup中柒室。
# 在剛剛創(chuàng)建好的hierarchy上cgroup根節(jié)點(diǎn)擴(kuò)展出兩個(gè)子cgroup渡贾。
$ cd ./cgroup-test/
$ mkdir cgroup-1
$ mkdir cgroup-2
$ tree
.
├── cgroup-1
│ ├── cgroup.clone_children
│ ├── cgroup.procs
│ ├── notify_on_release
│ └── tasks
├── cgroup-2
│ ├── cgroup.clone_children
│ ├── cgroup.procs
│ ├── notify_on_release
│ └── tasks
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks
可以看到在一個(gè)cgroup的目錄下創(chuàng)建文件夾時(shí),內(nèi)核會(huì)把文件夾標(biāo)記為這個(gè)cgroup的子group雄右,他們會(huì)繼承父cgroup的屬性空骚。
# 在cgroup中添加進(jìn)程
$ cat /proc/$$/cgroup
13:name=cgroup-test:/
12:memory:/user.slice
11:perf_event:/
10:cpuset:/
9:devices:/user.slice
8:blkio:/user.slice
7:pids:/user.slice/user-1000.slice/session-8.scope
6:rdma:/
5:hugetlb:/
4:freezer:/
3:cpu,cpuacct:/user.slice
2:net_cls,net_prio:/
1:name=systemd:/user.slice/user-1000.slice/session-8.scope
0::/user.slice/user-1000.slice/session-8.scope
$ echo $$ >> cgroup-1/tasks
$ cat /proc/$$/cgroup
13:name=cgroup-test:/cgroup-1
12:memory:/user.slice
11:perf_event:/
10:cpuset:/
9:devices:/user.slice
8:blkio:/user.slice
7:pids:/user.slice/user-1000.slice/session-8.scope
6:rdma:/
5:hugetlb:/
4:freezer:/
3:cpu,cpuacct:/user.slice
2:net_cls,net_prio:/
1:name=systemd:/user.slice/user-1000.slice/session-8.scope
0::/user.slice/user-1000.slice/session-8.scope
可以看到當(dāng)前進(jìn)程已經(jīng)被添加到cgroup-test:/cgroup-1中了。
# 通過subsystem限制cgroup中進(jìn)程的資源擂仍。
# 在上面創(chuàng)建hierarchy的時(shí)候囤屹,這個(gè)hierarchy并沒有關(guān)聯(lián)到任何的subsystem,所以沒有辦法通過那個(gè)hierarchy中的cgroup節(jié)點(diǎn)限制進(jìn)程的資源占用逢渔,其實(shí)系統(tǒng)已經(jīng)為每一個(gè)subsystem創(chuàng)建了一個(gè)默認(rèn)的hierarchy肋坚,比如memory的hierarchy。
$ mount|grep memory
$ cd /sys/fs/cgroup/memory
# 在不做限制的情況下肃廓,啟動(dòng)一個(gè)占用內(nèi)存的stress進(jìn)程
$ stress --vm-bytes 200m --vm-keep -m 1
$ mkdir test-limit-memory
$ cd test-limit-memory
# 設(shè)置最大的內(nèi)存占用為100M
$ echo 100m > memory.limit_in_bytes
# 把當(dāng)前的進(jìn)程移動(dòng)到這個(gè)cgroup中
$ echo $$ > tasks
$ stress --vm-bytes 200m --vm-keep -m 1
docker 使用cgroup智厌,(以內(nèi)存為例子)
$ docker run -itd -m 100m docker-proxy.repo.inspur.com/busybox
$ cd /sys/fs/cgroup/memory/docker/<container ID>/
$ cat memory.limit_in_bytes
104857600
靜態(tài)(鏡像)
Mount Namespace
Mount Namespace 修改的,是容器進(jìn)程對(duì)文件系統(tǒng)“掛載點(diǎn)”的認(rèn)知盲赊。在 Linux 操作系統(tǒng)里铣鹏,有一個(gè)名為 chroot 的命令可以幫助你在 shell 中方便地完成這個(gè)工作。顧名思義哀蘑,它的作用就是幫你“change root file system”诚卸,即改變進(jìn)程的根目錄到你指定的位置葵第。Mount Namespace 正是基于對(duì) chroot 的不斷改良才被發(fā)明出來的,它也是 Linux 操作系統(tǒng)里的第一個(gè) Namespace合溺。
為了能夠讓容器的這個(gè)根目錄看起來更“真實(shí)”卒密,我們一般會(huì)在這個(gè)容器的根目錄下掛載一個(gè)完整操作系統(tǒng)的文件系統(tǒng),比如 Ubuntu16.04 的 ISO棠赛。這樣哮奇,在容器啟動(dòng)之后,我們?cè)谌萜骼锿ㄟ^執(zhí)行 "ls /" 查看根目錄下的內(nèi)容恭朗,就是 Ubuntu 16.04 的所有目錄和文件屏镊。
掛載在容器根目錄上,用來為容器進(jìn)程提供隔離后執(zhí)行環(huán)境的文件系統(tǒng)就是 容器鏡像痰腮。
rootfs
rootfs 只是一個(gè)操作系統(tǒng)所包含的文件而芥、配置和目錄,并不包括操作系統(tǒng)內(nèi)核膀值。在 Linux 操作系統(tǒng)中棍丐,這兩部分是分開存放的,操作系統(tǒng)只有在開機(jī)啟動(dòng)時(shí)才會(huì)加載指定版本的內(nèi)核鏡像沧踏。
同一臺(tái)機(jī)器上的所有容器歌逢,都共享宿主機(jī)操作系統(tǒng)的內(nèi)核。這就意味著翘狱,如果你的應(yīng)用程序需要配置內(nèi)核參數(shù)秘案、加載額外的內(nèi)核模塊,以及跟內(nèi)核進(jìn)行直接的交互潦匈,你就需要注意了:這些操作和依賴的對(duì)象阱高,都是宿主機(jī)操作系統(tǒng)的內(nèi)核,它對(duì)于該機(jī)器上的所有容器來說是一個(gè)“全局變量”茬缩,牽一發(fā)而動(dòng)全身
正是由于 rootfs 的存在赤惊,容器才有了一個(gè)被反復(fù)宣傳至今的重要特性:一致性。
由于 rootfs 里打包的不只是應(yīng)用凰锡,而是整個(gè)操作系統(tǒng)的文件和目錄未舟,也就意味著,應(yīng)用以及它運(yùn)行所需要的所有依賴掂为,都被封裝在了一起裕膀。
但是,如果修改了某個(gè)文件需要重新制作一次rootfs勇哗。新舊rootfs之間就沒有任何關(guān)系了昼扛。不過既然所有的修改都基于一個(gè)舊的rootfs,我們能不能用增量的方式去做這些修改智绸?正是因?yàn)檫@個(gè)野揪,Docker鏡像在設(shè)計(jì)的時(shí)候引用了層(layer)的概念。用到了一種叫做聯(lián)合文件系統(tǒng)(Union FIle system 也叫UnionFS)的技術(shù)瞧栗。
Union File System
Union File System最主要的功能是將多個(gè)不同位置的目錄聯(lián)合掛載(union mount)到同一個(gè)目錄下斯稳。比如,我現(xiàn)在有兩個(gè)目錄 A 和 B迹恐,它們分別有兩個(gè)文件:
$ tree
.
├── A
│ ├── a
│ └── x
└── B
├── b
└── x
然后挣惰,我使用聯(lián)合掛載的方式,將這兩個(gè)目錄掛載到一個(gè)公共的目錄 C 上:
$ mkdir C
$ mount -t aufs -o dirs=./A:./B none ./C
再查看目錄 C 的內(nèi)容殴边,就能看到目錄 A 和 B 下的文件被合并到了一起:
$ tree ./C
./C
├── a
├── b
└── x
對(duì)于一個(gè)鏡像憎茂。它最關(guān)鍵的目錄結(jié)構(gòu)在/var/lib/docker/
一個(gè)容器的rootfs可以分為三部分:
第一部分,只讀層:
這一部分完全繼承自鏡像
第二部分锤岸,可讀寫層竖幔。
它是容器的最上面一層,在沒有寫入文件之前是偷,這個(gè)目錄是空的拳氢。而一旦在容器里做了寫操作,你修改產(chǎn)生的內(nèi)容就會(huì)以增量的方式出現(xiàn)在這個(gè)層中蛋铆。如果是刪除馋评,可讀寫層里會(huì)創(chuàng)建一個(gè) whiteout 文件,把只讀層里的文件“遮擋”起來刺啦。
第三部分留特,Init 層。
它是一個(gè)以“-init”結(jié)尾的層玛瘸,夾在只讀層和讀寫層之間蜕青。Init 層是 Docker 項(xiàng)目單獨(dú)生成的一個(gè)內(nèi)部層,專門用來存放 /etc/hosts捧韵、/etc/resolv.conf 等信息市咆。
最終。這三部分會(huì)聯(lián)合掛載到一個(gè)目錄下再来,表現(xiàn)為一個(gè)完成的鏡像供容器使用
使用dockerfile build鏡像時(shí)蒙兰,為了控制鏡像的大小,刪除文件的命令應(yīng)該和增加這個(gè)文件的命令在同一條語句芒篷。
一個(gè)反例:
如何找到鏡像對(duì)應(yīng)的哪幾層搜变?
$ docker inspect <image id>
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:195be5f8be1df6709dafbba7ce48f2eee785ab7775b88e0c115d8205407265c5",
"sha256:0faad07fc16f96bdb8e433cc55068528472eb3756cc36b7e0134e3581f2babf0",
"sha256:876b5eb1778f3084b020cf291c2d1e241c964092b0de8044305aba1f620202e5"
]
},
...
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/<layers-id>/cache-id
容器網(wǎng)絡(luò)
在上面的介紹中,通過Namespace和Cgroup技術(shù)實(shí)現(xiàn)了容器進(jìn)程的隔離针炉,并且通過聯(lián)合文件掛載系統(tǒng)讓容器擁有自己的文件系統(tǒng)挠他,容器中的進(jìn)程所感受到的環(huán)境就像在一臺(tái)虛擬機(jī)上一樣,但是篡帕,這臺(tái)虛擬機(jī)還沒有插上網(wǎng)線殖侵。下面就給這個(gè)虛擬機(jī)插上網(wǎng)線贸呢。
前面介紹了Network Namespace,可以給容器分配獨(dú)立的網(wǎng)絡(luò)空間拢军,那么怎么給這個(gè)網(wǎng)絡(luò)空間增加網(wǎng)絡(luò)配置呢楞陷。
Linux 虛擬網(wǎng)絡(luò)設(shè)備
Linux Veth
Veth是成對(duì)出現(xiàn)的網(wǎng)絡(luò)虛擬設(shè)備,發(fā)送到Veth一端虛擬設(shè)備的請(qǐng)求會(huì)送另一端的虛擬設(shè)備中發(fā)出茉唉。在容器化的虛擬場(chǎng)景中固蛾,經(jīng)常會(huì)使用Veth連接不同網(wǎng)絡(luò)的namespace。
# 創(chuàng)建兩個(gè)網(wǎng)絡(luò)namespace
$ ip netns add ns1
$ ip netns add ns2
#查看創(chuàng)建的網(wǎng)絡(luò)namespace
$ ip netns ls
$ ls /var/run/netns/ns
# 創(chuàng)建一對(duì)Veth
$ ip link add veth0 type veth peer name veth1
# 分別將兩個(gè)Veth移動(dòng)到兩個(gè)namespace中
$ ip link set veth0 netns ns1
$ ip link set veth1 netns ns2
# 去ns1中查看網(wǎng)絡(luò)設(shè)備
$ ip netns exec ns1 ip link
# 配置每個(gè)veth的網(wǎng)絡(luò)地址和namepace路由
$ ip netns exec ns1 ifconfig veth0 172.18.0.2/24 up
$ ip netns exec ns2 ifconfig veth1 172.18.0.3/24 up
# dev是設(shè)備 區(qū)別于gw 是網(wǎng)關(guān)
$ ip netns exec ns1 route add default dev veth0
$ ip netns exec ns2 route add default dev veth1
$ ip netns exec ns2 ip addr
Linux Bridge
Bridge虛擬設(shè)備是用來連接橋接的網(wǎng)絡(luò)設(shè)備度陆,它相當(dāng)于交換機(jī)艾凯,可以連接不同的網(wǎng)絡(luò)設(shè)備,當(dāng)請(qǐng)求到達(dá)Bridge設(shè)備時(shí)懂傀,可以通過報(bào)文中的Mac地址進(jìn)行廣播或轉(zhuǎn)發(fā)趾诗。
# 創(chuàng)建veth設(shè)備并將一段移入namespace
$ ip netns add ns3
$ ip link add veth2 type veth peer name veth3
$ ip link set veth3 netns ns3
# 創(chuàng)建網(wǎng)橋
$ brctl addbr br0
# 掛載網(wǎng)絡(luò)設(shè)備
$ brctl addif br0 veth2
linux 路由表
路由表是Linux內(nèi)核的一個(gè)模塊,通過定義路由表來決定某個(gè)網(wǎng)絡(luò)Namespace中包的流向鸿竖,從而定義請(qǐng)求會(huì)到哪個(gè)網(wǎng)絡(luò)設(shè)備上沧竟。
# 啟動(dòng)虛擬網(wǎng)絡(luò)設(shè)備,并設(shè)置它在Net Namespace中的IP地址缚忧。
$ ip link set veth2 up
$ ip link set br0 up
$ ip netns exec ns3 ifconfig veth3 172.18.0.4/24 up
# 分別設(shè)置ns1網(wǎng)絡(luò)空間的路由和宿主機(jī)的路由
# default代表0.0.0.0/0 即在Net Namespace 中所有流量都經(jīng)過veth1的網(wǎng)絡(luò)設(shè)備流出
$ ip netns exec ns3 route add default dev veth3
# 在宿主機(jī)上將172.18.0.0/24的網(wǎng)段請(qǐng)求路由到br0網(wǎng)橋
$ route add -net 172.18.0.0/24 dev br0
# 查看宿主機(jī)的IP地址
$ ifconfig ens33
# 從Namespace中訪問宿主機(jī)
$ ip netns exec ns3 ping -c 1 192.168.141.146
$ ping -c 172.18.0.4
Linux iptables
iptables 是對(duì)Linux內(nèi)核的netfilter模塊進(jìn)行操作和展示的工具悟泵,用來管理包的流動(dòng)和傳送。iptables定義了一套鏈?zhǔn)教幚淼慕Y(jié)構(gòu)闪水,在網(wǎng)絡(luò)包傳輸?shù)母鱾€(gè)階段可以使用不同的策略對(duì)包進(jìn)行加工糕非、傳送或丟棄。在容器虛擬化的技術(shù)中球榆,經(jīng)常會(huì)用到兩種策略MASQUERADE和DNAT
MASQUERADE
iptables中的MASQUERADE策略可以將請(qǐng)求包中的源地址轉(zhuǎn)換成一個(gè)網(wǎng)絡(luò)設(shè)備的地址朽肥。上邊介紹的那個(gè)Namespace中網(wǎng)絡(luò)設(shè)備的地址是172.18.0.4.這個(gè)地址雖然在宿主機(jī)上可以路由到br0網(wǎng)橋,倒是到達(dá)宿主機(jī)外部之后持钉,是不知道如何路由到這個(gè)IP地址的衡招,如果請(qǐng)求外部的地址的話需要先通過MASQUERADE策略將這個(gè)IP轉(zhuǎn)化成宿主機(jī)出口網(wǎng)卡的IP
# 打開ip轉(zhuǎn)發(fā)
$ sysctl -w net.ipv4.conf.all.forwarding=1
# 對(duì)Namespace中發(fā)出的包添加網(wǎng)絡(luò)地址轉(zhuǎn)換
# 在名為POSTROUTING的nat表?xiàng)單蔡砑硬呗詾镸ASQUERADE的匹配,將來自172.18.0.0/24的包通過 ens33 網(wǎng)卡發(fā)出
$ iptables -t nat -A POSTROUTING -s 172.18.0.0/24 -o ens33 -j MASQUERADE
在Namespace中請(qǐng)求宿主機(jī)外部地址時(shí)每强,將Namespace中的源地址轉(zhuǎn)換成宿主機(jī)的地址作為源地址始腾,既可以在Namespace中訪問宿主機(jī)外的網(wǎng)絡(luò)了。
DNAT
DNAT策略也是做網(wǎng)絡(luò)地址的轉(zhuǎn)換空执,不過他是要更換不妙地址浪箭,經(jīng)常用于將內(nèi)部網(wǎng)絡(luò)地址的端口映射到外部去。
# 將宿主機(jī)上的80端口的請(qǐng)求轉(zhuǎn)發(fā)到Namespace的IP上
$ iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.4:80
iptables -nvL -t nat
Docker的架構(gòu)圖:
C/S之間可以通過三種方式連接:
- unix:///val/run/docker.sock
- tcp://host:port
- fd://socketfd
Docker 常用命令
# 啟動(dòng)容器
# -i 顯示 console
# -t 交互
# --rm 容器退出后刪除容器
# container name 可以自定義
# image 可以是image name 也可以是image id
# cmd 是容器運(yùn)行的命令辨绊,跟在entrypoint之后奶栖。(dockerfile 時(shí)詳細(xì)解釋)
$ docker run -it --rm -n <container name> <image> <CMD>
# -d 后臺(tái)運(yùn)行
# --restart=always,容器異常退出時(shí),重啟容器宣鄙,比如dockerd重啟之后袍镀。
# -p 端口映射 宿主機(jī)端口:容器內(nèi)端口
# -v 掛載volume 宿主機(jī)目錄:容器內(nèi)目錄。必須時(shí)絕對(duì)路徑冻晤,當(dāng)前目錄可以用$(pwd)
# --cpu 設(shè)置cpu最大限制
# -m 設(shè)置內(nèi)存最大限制流椒。
$ docker run -d --restart=always -p 8080:8080 -v $(pwd):/root/ --cpu=1 -m 10m -n <container name> <image>
# 停止容器
# container可以是 container name,也可以是container ID
$ docker stop <container>
# 查看容器日志
# -f 可以實(shí)時(shí)顯示
# --tail=10 只顯示最后10行
$ docker logs --tail=10 <container> -f
# 進(jìn)入容器
$ docker exec -it <container> /bin/sh
# 刪除容器
# -f 參數(shù)可以強(qiáng)制停止正在運(yùn)行中的容器并刪除
$ docker rm -f <container>
#鏡像重命名
$ docker tag <source image name/id> <target image name>
# 構(gòu)建鏡像
# -t 可以指定都建出來的鏡像的name及tag
$ docker build <Dockerfile path> -t <image name>
# 查看容器/鏡像的詳細(xì)信息
$ docker inspect <container or image>
# 查看docker server的詳細(xì)信息
$ docker info
# 查看docker 占用的磁盤情況
# -v 列出詳細(xì)信息
$ docker system df -v
# 刪除沒用的數(shù)據(jù),包括鏡像明也、容器。
$ docker system prune
# 從容器內(nèi)外復(fù)制文件
$ docker cp CONTAINER:SRC_PATH DEST_PATH
$ docker cp SRC_PATH CONTAINER:DEST_PATH
# 列出所有 image
$ docker images
# 列出所有容器
# -a 顯示所有容器
$ docker ps -a
# 列出容器資源占用情況
# container 可選
$ docker stats <container>
Dockerfile
FROM:指定基礎(chǔ)鏡像
第一條指令惯裕。scratch是虛擬的鏡像温数,表示一個(gè)空白的鏡像。
LABEL: 添加元數(shù)據(jù)
LABEL <key>=<value> <key>=<value> <key>=<value> ...
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
一般需要在LABEL中至少添加 Name和Version兩條元數(shù)據(jù)
RUN:執(zhí)行命令
shell 格式: RUN <命令> 蜻势,RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec 格式: RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"] 撑刺。run可以寫多個(gè),每一個(gè)指令都會(huì)建立一層握玛,所以正確寫法應(yīng)該是↓
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
COPY:復(fù)制文本
COPY <源路徑>... <目標(biāo)路徑>
COPY ["<源路徑1>",... "<目標(biāo)路徑>"]
<源路徑> 可以是多個(gè)够傍、以及使用通配符,通配符規(guī)則滿足Go的filepath.Match 規(guī)則挠铲,如:COPY hom* /mydir/ COPY hom?.txt /mydir/
<目標(biāo)路徑>使用 COPY 指令冕屯,源文件的各種元數(shù)據(jù)都會(huì)保留。比如讀拂苹、寫安聘、執(zhí)行權(quán)限、文件變更時(shí)間等瓢棒。
ADD:高級(jí)復(fù)制文件
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
<源路徑> 可以是一個(gè) URL 浴韭,如果是tgz的文件可以自動(dòng)解壓。下載后的文件權(quán)限自動(dòng)設(shè)置為 600 脯宿。
CMD:容器啟動(dòng)命令
shell 格式: CMD <命令>
exec 格式: CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT:入口點(diǎn)
同CMD念颈,指定容器啟動(dòng)程序及參數(shù)。
通過--entrypoint 參數(shù)在運(yùn)行時(shí)替換连霉。
用例一:使用CMD要在運(yùn)行時(shí)重新寫命令才能追加運(yùn)行參數(shù)榴芳,ENTRYPOINT則可以運(yùn)行時(shí)接受新參數(shù)。
示例:
FROM ubuntu:16.04RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
追加-i參數(shù)
$ docker run myip -i
......
當(dāng)前 IP:61.148.226.66 來自:北京市 聯(lián)通
ENV:設(shè)置環(huán)境變量
在其他指令中可以直接引用ENV設(shè)置的環(huán)境變量窘面。
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
示例:
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
ARG:構(gòu)建參數(shù)
與ENV不同的是翠语,容器運(yùn)行時(shí)不會(huì)存在這些環(huán)境變量〔票撸可以用 docker build --build-arg <參數(shù)名>=<值> 來覆蓋肌括。
VOLUME:定義匿名卷
容器運(yùn)行時(shí)應(yīng)該盡量保持容器存儲(chǔ)層不發(fā)生寫操作,對(duì)于數(shù)據(jù)庫類需要保存動(dòng)態(tài)數(shù)據(jù)的應(yīng)用,其數(shù)據(jù)庫文件應(yīng)該保存于卷(volume)中谍夭。
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
volume和run -v參數(shù)的區(qū)別黑滴。
VOLUME指令只是起到了聲明了容器中的目錄作為匿名卷,但是并沒有將匿名卷綁定到宿主機(jī)指定目錄的功能紧索。但是當(dāng)我們生成鏡像的Dockerfile中以Volume聲明了匿名卷袁辈,并且我們以這個(gè)鏡像run了一個(gè)容器的時(shí)候,docker會(huì)在安裝目錄下的指定目錄下面生成一個(gè)目錄來綁定容器的匿名卷珠漂。
EXPOSE:暴露端口
EXPOSE <端口1> [<端口2>...]
EXPOSE :EXPOSE 僅僅是聲明容器打算使用什么端口而已晚缩,并不會(huì)自動(dòng)在宿主進(jìn)行端口映射。
WORKDIR:指定工作目錄
WORKDIR <工作目錄路徑>
RUN cd /app
RUN echo "hello" > world.txt
兩次run不在一個(gè)環(huán)境內(nèi)媳危,可以使用WORKDIR荞彼。
USER:指定當(dāng)前用戶
這個(gè)用戶必須是事先建立好的,否則無法切換待笑。
USER <用戶名>
最佳實(shí)踐:
http://dockone.io/article/9658
Dockerfile 掃描
hadolint是目前為止成熟最高的一個(gè)工具鸣皂,一共支持60多條的自帶規(guī)則。而且hadolint還支持對(duì)Run命令中帶的shell命令進(jìn)行審計(jì)暮蹂。這一點(diǎn)功能是目前其他競(jìng)品不具備的寞缝。不過hadolint使用Haskell語言編寫,且目前不支持規(guī)則文件導(dǎo)入仰泻。
如果有新的規(guī)則需要編程實(shí)現(xiàn)荆陆。dockerlint 使用coffee_script語言編寫,自帶規(guī)則數(shù)量小于hadolint集侯;也不支持規(guī)則文件導(dǎo)入功能慎宾。
dockerfile_lint 紅帽發(fā)布,使用node.js編寫的一個(gè)dockerfile掃描工具浅悉。支持的規(guī)則數(shù)量小于hadolint趟据,但是支持通過yaml文件方式導(dǎo)入用戶自定義規(guī)則。
dockerfile_lint 工具的使用
dockerfile_lint代碼在github.com/projectatomic/dockerfile_lint下术健,有兩種方法執(zhí)行dockerfile_lint:
- 從dockerhub官方下載dockerfile_lint latest的docker鏡像汹碱。
- 在nodejs執(zhí)行環(huán)境上,運(yùn)行dockerfile_lint掃描工具荞估。
docker run -it --rm -v $(pwd):/target/ docker-proxy.repo.inspur.com/projectatomic/dockerfile-lint:latest /bin/bash
[root@4298ab08644f /]#dockerfile_lint -f /target/Dockerfile -v
dockerfile_lint 支持下面參數(shù)
-h 打印help信息
-j 結(jié)果以jason格式輸出
-r rulefile 指定一個(gè)規(guī)則文件咳促。
-v 打印debug新秀
-f 指定dockerfile的路徑
-e 打印出目前的規(guī)則
dockerfile_lint 規(guī)則
目前為止dockerfile_lint默認(rèn)情況下帶了default_rules.yaml和base_rules.yaml。如果不指定任何rulefile勘伺,那么系統(tǒng)默認(rèn)采用這兩個(gè)規(guī)則文件跪腹,在docker鏡像的/opt/dockerfile_lint/config/目錄下。此外在/opt/dockerfile_lint/sample_rules下面還帶了很多事例rule飞醉〕迦祝可以直接在容器內(nèi)部修改default_rules.yaml和base_rules.yaml文件,然后再運(yùn)行dockerfile_lint。
可以看到dockerfile_lint的規(guī)則基本上都是基于正則匹配的轴术,所以用戶可以很方便的擴(kuò)展新的規(guī)則难衰。
需要注意的是dockerfile_lint語法中required_instruction域中的count字段在19年12月截止的版本中是無效的。
dive
https://github.com/wagoodman/dive
dive <image>
Docker daemon的常用配置
{
"dns": ["10.100.1.12"], # 設(shè)定容器DNS的地址逗栽,在容器的 /etc/resolv.conf文件中可查看盖袭。
"insecure-registries": [],#配置docker的私庫地址,配置之后可以解決https免證書問題彼宠。
"registry-mirrors":["xxxx"], #鏡像加速的地址鳄虱,增加后在 docker info中可查看。
"graph": "/mnt/ssd/0/docker", #docker 文件存放地址
}