Docker 核心知識回顧
最近公司為了提高專案治理能力、提升開發效率,將之前的CICD專案擴充套件成devops進行專案管理。開發人員需要對自己的負責的專案進行流水線的部署,包括寫Dockerfile 對自己的服務製作服務映象。之前看過的東西,一段時間不用現在突然用起來還有些生疏。此篇對之前的Docker知識進行回顧加深。
對於docker 基本使用命令不再提及,遇到命令忘記或者不知道含義的時候可以使用 help 來進行檢視。
基本架構
Docker 採用的是經典的C/S架構,包括客戶端 和 服務端兩大 核心元件。
Containers-shim:是containerd的子程式,為runc容器提供支援,也是容器內程式的 根程式
Dockerfiel
這裡主要說一下Dockerfile 的編寫注意的事項:
-
EXPOSE:只是申明映象內監聽埠,並不會完成自動對映。
-
ENV:當一條EVN指令 中同時為多個環境變數賦值 並且 值也是從環境變數中讀取,會為變數都賦值後才更新
ENV key1 = valu1
ENV key1= valu2
ENV key2 = ${key1}
此時key1=valu2,key2=valu1
-
Context: 因為Docker是 C/S 架構的,在編寫完Dockerfile 使用build 命令建立映象的時候會將Dockerfile 所在路徑下的資料作為上下文,傳輸給 服務端來建立映象。所以如果我們Dockerfile同級目錄下有多個檔案,最好使用.dockerignore 來進行忽略,防止過多的資料傳送到 docker服務端。
-
ADD\COPY : 都支援 go 語言格式的正規表示式。還有要注意路徑的問題。
因為dockerfile 可以多步驟建立,所以最好 進行單一職責的劃分,製作的映象省略掉中間的環境,這樣可以精簡最終映象的大小。
名稱空間(重要)
名稱空間是(namespace)是linux 核心的一個強大 特性。
作業系統中,包括核心,檔案系統、網路、程式號、使用者號、程式間通訊 等資源都是程式間 直接共享的。想要虛擬化,那麼除對 記憶體、cpu、網路IO等進行限制分割外,還需要實現檔案系統、網路、PID、UID、IPC 等相互隔離。前面的好做限制,關鍵是後面的 檔案系統、網路之類的如何隔離,這就需要系統的支援,也就是名稱空間的引入了。
-
程式名稱空間(較為重要):
每個程式名稱空間有一套自己的程式號管理方法,
我們從 前面 基本架構 可以看到,他們的程式是進行繼承的。
子空間對於父親空間是可見,父空間對子空間不可見
linux 通過程式名稱空間管理程式號,對於同一程式,在不同名稱空間中,看到的程式號不一樣。
$ ps -ef|grep docker root 3393 1 0 Jan18 ? 0:43:02 /usr/bin/dcokerd .. root 3398 3393 0 Jan18 ? 0:34:32 docker-containerd ...
我們在建立一個新的容器,執行 sleep 命令,然後在看看容器的 程式號(注意檢視 父程式號)
$ docker run --name test -d linux sleep 9999 $ ps -ef|grep docker root 21535 3398 0 0:57 ? docker-containerd-shim....
然後我們在 宿主機 檢視新建容器的程式,也是 docker-containerd-shim 程式
$ ps -ef|grep sleep 9999 root 21569 21535 0 06:57 ? sleep 9999
重點:我們在容器內 檢視程式
$ docker exec -it 3a bash -c 'ps -ef' UID PID PPID C STME TTY TIME CMD root 1 0 0 06:57 ? 00:00:00 sleep 9999
可以使用 pstree 命令,檢視到完整的程式樹
-
IPC名稱空間:
容器中 程式互動 還是使用linux 程式間的互動方法,包括訊號量、訊息佇列。同一個IPC名稱空間,程式可以彼此可見,不同的則無法訪問。
-
網路名稱空間(重點)
有了程式間的名稱空間,不用名稱空間的程式訊號可以相互隔離,但是,網路埠還是公用的,所以可以使用網路名稱空間。
docker 採用虛擬網路裝置,將不用名稱空間的網路裝置連線到一起。(預設網橋)
docker 可以使用四種網路模式:
-
Host :和主機公用一個網路,容器沒有虛擬的網路卡,沒有獨立的ip,和主機的網路是一樣的。(但是檔案之類的還是隔離的)
-
Container模式:和其他已存在的容器共享一個 Network Namespace, 不是和主機共享。
-
None模式:放在自己容器的網路內部中,外部訪問不到,內部也訪問不到外部。容器內部只能使用loopback網路裝置不會再有其他網路資源。只能使用127.0.0.1的本機網路
-
Bridge模式:容器獨立的使用 network Namespace,並連結到docker0虛擬網路卡,通過docker0網橋以及Iptables nat表配置與宿主機通訊;bridge模式是Docker預設的網路設定
當Docker server啟動時,會在主機上建立一個名為docker0的虛擬網橋,
此主機上啟動的Docker容器會連線到這個虛擬網橋上。
虛擬網橋的工作方式和物理交換機類似,這樣主機上的所有容器就通過交換機連在了一個二層網路中。
接下來就要為容器分配IP了,
Docker會從RFC1918所定義的私有IP網段中,選擇一個和宿主機不同的IP地址和子網分配給docker0,
連線到docker0的容器就從這個子網中選擇一個未佔用的IP使用。
如一般Docker會使用172.17.0.0/16這個網段,並將172.17.0.1/16分配給docker0網橋(在主機上使用ifconfig命令是可以看到docker0的,可以認為它是網橋的管理介面,在宿主機上作為一塊虛擬網路卡使用)。
這裡容器的訪問控制 主要通過linux的 iptables 防火牆軟體來控制的,
-
容器間的訪問,這裡是需要兩個方面的支援
-
網路拓撲是否已經聯通(預設都連結到docker0上一般都是互通的)
-
本地系統的防火牆軟體iptables 是否允許訪問通過,這取決於防火牆的規則
-
訪問所有埠
當啟動docker ,預設會新增一條‘允許’轉發策略到iptables的 forward 鏈上,通過配置 -- icc=true|false 引數控制(啟動docker 手動指定 iptables規則,不會影響 宿主機的iptables規則)
-
訪問指定埠
可以通過 --link=container_name:allas 指定。(兩個容器之間通過新增一條 ACCEPT規則)
-
-
-
對於容器訪問外部。
轉發過程:我們可以從上圖看到,容器將請求通過 veth pair 介面給到docker 網橋,然後網橋通過docker0 傳送到宿主機物理網路卡上(其實dock er0 對應的就是一個網路卡的埠) 網橋就是和交換機類似的作用。
- 這裡請求要到外部,需要宿主機進行輔助轉發,在宿主機器內檢視是否允許 轉發
sudo sysctl net.ipv4.ip_forward
- forward =1 則是轉發,0則是關閉轉發。
轉發IP 變化:外部訪問內部肯定不止直接訪問 容器的IP了,需要進行源地址對映 SNAT(Source NAT),修改為宿主機 IP地址 10.0.2.2
具體操作:內部容器請求到達到主機向外部傳送請求前,主機的ipstable 偽裝源地址,ipstable 的 nat 表新增規則,將其源地址改為 主機地址 10.0.2.2(這個規則適用所用從docker 網橋的請求ip)
#iptables -t nat -A POSTROUTING -s 127.17.0.1/16 -o eth1 -j SNAT --to-source 10.10.0.186 ## 解釋規則:就是給nat表中 POSTROUTING 鏈 新增一條規則:從 s 過來的網段 (127.17.0.1/16) 都進行 snat 動作,即轉換ip 為10.10.0.186
上邊是針對企業中常應用的,但在家庭當中,很少有固定地址,一般都是動態地址,也就是說,出去的跳板是變動的,這樣剛才所設定的規則就不行了,不過現在可以通過一個叫做 MASQUERADE—- 地址偽裝來解決,即 snat 換成 MASQUERADE。
- 這裡請求要到外部,需要宿主機進行輔助轉發,在宿主機器內檢視是否允許 轉發
-
外部訪問內部容器。
我們通過 容器啟動時對映埠命令 -p 來新增容器到本機的埠對映,這其實也是在本地的 ipstable 新增 nat 規則,將外部IP 進行目標地址DNAT,將目標地址修改為容器內部ip 地址。
這裡nat表設計兩條鏈:
- PREROUTING 鏈 負責包到 網路介面時,改寫器目的地址,其中的規則流量都到 docker 鏈,
- Docker 鏈將所有不是從docker0 進來的包(非本機器的產生的包),同時目標 埠為 docker0 對映的物理埠號(或者容器對映的埠號),修改目標地址為 172.2.0.2,目標埠使用 容器對映埠。
-
該圖片來源於網路【https://blog.csdn.net/beanewself/article/details/78317626】
報文流向:
流入本機:PREROUTING --> INPUT-->使用者空間程式 流出本機:使用者空間程式-->OUTPUT--> POSTROUTING 轉發:PREROUTING --> FORWARD --> POSTROUTING
不過還是建議,自定義一個網橋,這樣方便自己管理容器的網路 。(使用openvswitch)
DNS
-
docker 服務啟動後會預設啟用一個 內嵌的 dns 服務,來自動解析同一個網路中的容器主機名和地址,如果無法解析,則通過容器內的dns 相關配置進行解析。
-
Docker啟動容器時,會從宿主機 複製/etc/resolv.conf 檔案,並刪除掉無法連結的Dns 伺服器。
-
掛載名稱空間
掛載名稱空間允許 不同名稱空間的程式看到的本地檔案位於宿主機的不同路徑下,每個名稱空間的程式看到的目錄是彼此隔離的。
這裡有 聯合檔案系統的知識,網路上很多講解,這裡我自己的理解為:
Docker 容器內部使用 聯合檔案系統,我們宿主機上看到的還是一個檔案目錄,只不過在docker 容器中相互隔離了。
這裡要注意一點,對於可寫層要讀取下面的物件,如果 較為深層的物件 資料太大,意味著較差的IO效能。所以對於IO敏感型,推薦將容器通過 volume 方式掛載。
-
UTS名稱空間
UTS 名稱空間 允許每個容器擁有獨立的主機名和域名,從而可以虛擬出一個獨立的主機名 和網路空間的環境
-
使用者名稱空間
每個容器可以有不同的使用者 和 組ID,也就是說,可以在容器內使用特定的內部使用者 執行程式,而非本地系統存在的使用者
控制組
這個是linux 核心的一個特性,主要用來對共享資源進行隔離、限制、審計。
-
資源限制:可以將組設定一定對記憶體限制,記憶體子系統可以對對程式組 設定一個記憶體使用上線
-
優先順序:通過優先順序 讓一些組 優先得到更多的cpu 資源
-
資源審計:用來統計系統實際上把多少資源用到合適的目的上。
-
隔離:為組隔離名稱空間,使得另一個組不會看到程式、網路等
-
控制:執行掛起、恢復 和重啟
使用者可以 /sys/fs/cgroup/memory/docker/目錄下看到Docker組應用的各種限制項,使用者可以修改這些值,來進行限制docker 應用資源。
compose
作為Docker 三劍客之一,它最主要的功能是服務編排。
這裡只是簡單的介紹 和 說明一些常用的語法
我們通過Dockerfile 可以快速的編寫一個應用的映象,但是我們的服務往往是 多個服務協作進行的:
比如前後端分離:前端一個服務、後端一個服務、再有一個資料庫。。。
所以如果一個一個的寫dockerfile 那麼部署的時候也要進行先後配置,這顯然不是Devpos初衷,我們想要的是一鍵部署,所以這就用到compose了。
我們可以使用compose 將各個服務進行依賴編寫,然後同時部署多個容器。(在compose中,這叫做服務棧)
- 任務:一個容器被稱為一個任務,任務有個獨一無二的ID
- 服務:某個相同映象的容器副本(一個前端,對應多個後端,多個後端就是副本)
- 服務棧:多個服務組成,相互配合完成特定業務。
使用一個web 應用作為例子:
version: '3' ##使用的compose版本
services: ## 定義一個服務
mall-admin: ## 服務容器配置資訊
image: mall/mall-admin:1.0-SNAPSHOT ##映象,也可通過build 構建映象,
container_name: mall-admin
ports: ##容器埠
- 8080:8080
volumes: ##任務掛載路徑
- /mydata/app/mall-admin/logs:/var/logs
- /etc/localtime:/etc/localtime
environment: ##啟動入口
- 'TZ="Asia/Shanghai"'
external_links: ## 連結,通過這個可以做任務之間的依賴,容器之間可以訪問。
- mysql:db #可以用db這個域名訪問mysql服務
- nacos-registry:nacos-registry #可以用nacos-registry這個域名訪問nacos服務
mysql:
image: mysql:5.7
container_name: mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always
environment:
MYSQL_ROOT_PASSWORD: root #設定root帳號密碼
ports:
- 3306:3306
volumes:
- /mydata/mysql/data/db:/var/lib/mysql #資料檔案掛載
- /mydata/mysql/data/conf:/etc/mysql/conf.d #配置檔案掛載
- /mydata/mysql/log:/var/log/mysql #日誌檔案掛載
swarm
我們上面解決了服務棧,也就是服務之間的依賴問題,但是我們現在都是微服務,需要的是一個服務部署多個機器,如果有上百個服務,成千的機器群,那我們部署排查,那不得忙的不可開交了麼。所以docker 推出了swarm 來解決這個問題,就是對服務叢集部署的解決。
Swarm 叢集是一組被統一管理起來的docker 主機,叢集是swarm 所管理的 物件,這些主機通過docker引擎的swarm模式相互溝通,
說白了,swarm是定義一個服務 部署多少個節點(部署在多少個主機上),然後對每個節點的容器服務進行監控管理的。這才是docker 真正運用在企業生產的地方。
Kubernetes
和swarm 擁有相同 的能力,只不過它更優秀,是谷歌公司開源的專案。
使用者可以將配置模版提交之後,kubernetes 會自動管理(包括部署、釋出、伸縮、更新)應用容器來維護指定狀態。實現了十分高的可靠性 ,使用者無需關心細節。
他的核心概念:每個物件包括三大屬性:後設資料、規範、狀態。通過這三個屬性,使用者可以定義讓某個物件處於給定的狀態。這些物件儲存在 Etcd高可用鍵值儲存物件上(就是key-value形式,這個是分散式的儲存,採用簡潔的Raft共識演算法(這裡可以看之前的文章)),他自己本身也用的Raft共識演算法來保證 一致性。
這裡很重要,但是越來越覺得開發和運維分不開了,這完全是要開發做了運維的工作啊。。。目前用不到,之前嘗試搭建過環境,直接把我雲主機給幹崩了(3個4G記憶體的機器),等以後用的時候可以在深入的看用法。
參考源:
- 《Docker技術入門與實戰第三版》:機械工業出版社
- 網路部落格【https://blog.csdn.net/beanewself/article/details/78317626】