Docker 核心知識回顧

張小吉發表於2022-04-03

Docker 核心知識回顧

最近公司為了提高專案治理能力、提升開發效率,將之前的CICD專案擴充套件成devops進行專案管理。開發人員需要對自己的負責的專案進行流水線的部署,包括寫Dockerfile 對自己的服務製作服務映象。之前看過的東西,一段時間不用現在突然用起來還有些生疏。此篇對之前的Docker知識進行回顧加深。

對於docker 基本使用命令不再提及,遇到命令忘記或者不知道含義的時候可以使用 help 來進行檢視。

基本架構

Docker 採用的是經典的C/S架構,包括客戶端 和 服務端兩大 核心元件。
image

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的,可以認為它是網橋的管理介面,在宿主機上作為一塊虛擬網路卡使用)。
    image

    這裡容器的訪問控制 主要通過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,目標埠使用 容器對映埠。

image

image

該圖片來源於網路【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 方式掛載。

image

  • 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記憶體的機器),等以後用的時候可以在深入的看用法。

參考源:

  1. 《Docker技術入門與實戰第三版》:機械工業出版社
  2. 網路部落格【https://blog.csdn.net/beanewself/article/details/78317626】

相關文章