天天看點

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

作者:架構師尼恩

說在前面:

現在拿到offer超級難,甚至連面試電話,一個都搞不到。

尼恩的技術社群(50+)中,很多小夥伴憑借 “左手雲原生+右手大資料”的絕活,拿到了offer,并且是非常優質的offer,據說年終獎都足足18個月。

而雲原生的核心元件是 Docker + K8S,但是 Docker 又很難。在這裡,尼恩從架構師視角出發,Docker + K8S 核心原理做一個宏觀的介紹。

由于内容确實太多, 是以寫兩個pdf 電子書,并且後續會持續更新:

(1) 《 Docker 學習聖經 》PDF

(2) 《 K8S 學習聖經 》PDF

帶大家穿透Docker + K8S ,實作Docker + K8S 自由,讓大家不迷路。

本書 《 Docker 學習聖經 》PDF的 V1版本,後面會持續疊代和更新。供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水準。

注:本文以 PDF 持續更新,最新尼恩 架構筆記、面試題 的PDF檔案,請到《技術自由圈》公衆号領取

《 Docker 學習聖經 》PDF 封面

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

《 Docker 學習聖經 》目錄

- 說在前面:
- Docker基礎
- Docker 巨大的價值
  - Docker 的本質:
  - Docker的廣泛應用場景:
  - Docker的在DevOps(開發、運維)場景的應用
  - Docker 的曆史
- Docker 的入門知識
  - Docker Engine
  - Docker Platform
  - 到底什麼是docker:
  - 什麼是容器?
  - docker基本組成
  - Docker 與虛拟機有何差別
  - docker和kvm都是虛拟化技術,它們的主要差别:
  - 與傳統VM特性對比:
  - docker 與作業系統比較
- Docker 的安裝
  - 環境準備
  - docker安裝的三種方式
  - 方式一 :離線安裝docker
  - 方式二 :線上安裝docker
  - 方式三 :用現成的 (大大的省事)
- Docker Container概述
  - 什麼是Container 容器
  - 容器與鏡像的關系
- Docker本地容器相關的操作
  - Container相關指令
  - 建立容器
  - 檢視活躍容器 docker ps
  - 檢視全部容器
  - 停止容器
  - 删除容器
  - 檢視容器的程序資訊
  - 如何查找容器名稱?
- docker最為常用的幾個指令
  - docker的守護程序檢視
  - docker 鏡像檢視
  - docker 容器檢視
  - Docker Registry配置和檢視
- null
- Docker容器進入的4種方式
  - 方式1:使用docker attach進入Docker容器
  - 方式2:使用SSH進入Docker容器
  - 方式3:使用nsenter進入Docker容器
    - 1 什麼是nsenter?
    - 2、nsenter安裝
    - 3、nsenter的 使用
    - docker隔離應用應用涉及到的六大名稱空間
      - 1、pid 命名空間(程序ID)
      - 2、net 命名空間(網絡)
      - 3、ipc 命名空間(程序間通信)
      - 4、mnt 命名空間(挂載檔案系統)
      - 5、UTS 命名空間(主機名/域名)
      - 6、user 命名空間(使用者)
  - nsenter檢視docker的連接配接
  - 方式4:使用docker exec進入Docker容器
  - 在容器内部和主控端中檢視容器中的程序資訊
    - 檢視其父程序資訊
    - 檢視子程序資訊
    - 總計三個指令
- Docker本地鏡像載入與載出
  - 兩種辦法
  - 拉取鏡像
  - 儲存鏡像
  - 載入鏡像
  - 打個tag
  - 儲存鏡像
  - 載入鏡像
- Harbor私有鏡像倉庫
  - Harbor安裝
    - 1 下載下傳 Harbor的壓縮包;
    - 2 上傳壓縮包到虛拟機,并解壓;
    - 3 建立harbor通路域名證書
    - 4 配置harbor
    - 5 ./prepare 準備
    - 6 ./install.sh
    - 通路
    - 7 停止或者重新開機 Harbor
  - 修改docker配置檔案,使docker支援harbor
  - Harbor使用
    - 什麼是含有SAN的證書
  - SSL證書格式
  - 生成含有SAN的證書
    - 1.生成CA憑證私鑰。
    - 2、生成CA憑證
    - 3、生成伺服器證書
    - 生成證書簽名請求(CSR)
    - 4、使用該v3.ext檔案為Harbor主機生成證書cdh1.crt
    - 5、轉換cdh1.crt為cdh1.cert,供Docker使用
    - 6 運作prepare腳本以啟用HTTPS
    - 7 運作install.sh腳本來啟動harbor
    - 證書複制到 docker 并且啟動後登入
  - hostname push失敗
    - 以下為解決方法:
  - 推送鏡像到Harber
  - Docker 推送指令
  - 需要生成證書
  - 推送成功
- Docker Image概述
  - 什麼是Image
  - Image的擷取
  - 如何做一個自己的Base Image
- 建構自己的Docker鏡像
  - Dockerfile文法
  - 鏡像釋出
- Docker程序與主控端程序的對應關系
  - - Linux通過程序ID檢視檔案路徑
    - 容器的PID namespace(命名空間)
      - 找出容器ID
    - 檢視容器資訊
    - 進入相應目錄
    - 檢視容器目錄裡的程序号
    - 啟動一個程序
    - 檢視容器目錄裡的程序号
  - docker daemon (docker守護程序)
  - Docker檔案目錄和容器内部操作
- Docker Daemon 底層原理
  - 演進:Docker守護程序啟動
  - OCI(Open Container Initiative)
    - image spec
    - runtime spec
  - Docker CLI用戶端工具
  - Docker Daemon守護程序 (dockerd)
  - Containerd
  - docker-shim 容器程序
  - runc (OCI reference implementation)
  - Docker、containerd, containerd-shim和runc之間的關系
  - 通過runc來啟動一個container的過程
    - 檢視程序資訊
    - 檢視父程序資訊
    - 檢視程序樹
  - CRI 運作時接口
- Docker的技術底座:
  - 底層技術支援
- UnionFS 聯合檔案系統
  - 什麼是鏡像
  - UnionFS 與AUFS
  - 什麼是 Docker 鏡像分層機制?
  - Docker Image 如何而來呢?
- Namespaces
  - 程序隔離
  - 網絡隔離
  - Libnetwork
  - Chroot
- CGroups實體資源限制分組
- 總之:dockers=LXC+AUFS
- 深入解讀docker網絡
  - docker網絡理論部分
  - Docker網絡模式
    - bridge模式
    - host模式
    - Container網絡模式
    - none模式
    - overlay 網絡模式
    - macvlan 網絡模式
  - 網絡實操
    - bridge網絡
    - docker0詳解
    - 多容器之間通訊
    - link容器
      - 建立bridge網絡
      - 把一個運作中容器連接配接到lagou-bridge網絡
    - none網絡
    - host網絡
  - 網絡指令彙總
    - 檢視網絡
    - 建立網絡
    - 網絡删除
    - 檢視網絡詳細資訊
    - 使用網絡
    - 網絡連接配接與斷開
- Docker-Compose 簡介
  - Docker-Compose 用來實作Docker容器快速編排
    - Docker-compose模闆檔案簡介
    - eg:
    - Docker-Compose 的編排處出來的部署架構
- docker-compose 快速編排
  - Docker-Compose 的編排結構
  - YAML模闆檔案文法
  - docker-compose.yml 文法說明
    - 1、image
    - 2、build
    - 3、command
    - 4、links
    - 5、external_links
    - 6、ports
    - 7、expose
    - 8、volumes
    - 9、volunes_from
    - 10、environment
    - 11、env_file
    - 12、extends
    - 13、net
    - 14、pid
    - 15、dns
    - 16、cap_add,cap_drop
    - 17、dns_search
    - 18、healthcheck
    - 19、depends_on
    - 20、deploy
  - docker-compose.yml執行個體
  - YAML 檔案格式 及 編寫注意事項
  - Docker-compose常用指令
    - 1、Docker-Compose
    - 2、docker-compose up
    - 3、docker-compose ps
    - 4、docker-compose stop
    - 5、docker-compose -h
    - 6、docker-compose down
    - 7、docker-compose logs
    - 8、docker-compose build
    - 9、docker-compose pull
    - 10、docker-compose restart
    - 11、docker-compose rm
    - 12、docker-compose start
    - 13、docker-compose run
    - 14、docker-compose scale
    - 15、docker-compose pause
    - 16、docker-compose kill
    - 17、dokcer-compose config
    - 18、docker-compose create
    - 19、docker-compose exec
    - 20、docker-compose port
    - 21、docker-compose push
    - 22、docker-compose unpause
    - 23、docker-compose version
  - docker-compose logs 檢視日志
    - 全屏滾到底部結束
    - 全屏滾到底部并繼續持續輸出日志
    - 全屏滾到底部并繼續持續輸出日志并顯示時間戳
    - 全屏滾到底部顯示最後N行并繼續持續輸出日志并顯示時間戳
- 說在後面
- 參考資料           

Docker基礎

作為大神或者準架構師/架構師,一定要了解一下docker的底層原理。

首先還是簡單, 說明一下Docker 巨大的價值

Docker 巨大的價值

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker 是一個開源的應用容器引擎,基于 Go 語言開發。

Docker 遵從 Apache2.0 協定開源。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker 的本質:

先來說說Docker 的本質

Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後釋出到任何流行的 Linux 機器上,實作輕量級虛拟化。

docker為什麼有這麼巨大的價值呢?

因為,在容器技術出來之前,大家都是使用虛拟機技術,比如在 window中裝一個VMware,通過這個軟體我們可以虛拟出來一台或者多台電腦,實作硬體資源的細粒度分割和使用隔離。

但是 ,虛拟機技術太笨重啦!模式太重。

Docker容器技術,也是一種虛拟化技術,也是實作硬體資源的細粒度分割和使用隔離。但是,Docker是一種輕量級的虛拟機技術。

Docker容器是完全使用沙箱機制,互相之間不會有任何接口(類似 iPhone 的 app),更重要的是容器性能開銷極低。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker 從 17.03 版本之後分為 CE(Community Edition: 社群版) 和 EE(Enterprise Edition: 企業版),

對于開發人員來說,用 CE(Community Edition) 社群版就可以了

Docker的廣泛應用場景:

  • Web 應用的自動化打包和釋出。
  • 自動化測試和持續內建、釋出。
  • 在服務型環境中部署和調整資料庫或其他的背景應用。
  • 從頭編譯或者擴充現有的 OpenShift 或 Cloud Foundry 平台來搭建自己的 PaaS 環境。

一般來說,測試環境、生産環境,基本都已經全面docker化了。

可見,docker技術,是那麼那麼的重要。

Docker的在DevOps(開發、運維)場景的應用

一般來說,怎麼的微服務應用,有多套環境:

(1)開發
(2)測試
(3)預生産
(4)生産           

四套環境,導緻環境配置是十分的麻煩,每一個環境都要部署各種元件(如Redis、ES、zk) ,非常的費時費力。

更要命的是,在生産環境上, 吞吐量一上來,需要動态擴容。

使用docker,咱們可以将DevOps(開發、運維)的工作,高速完成:

(1)快速完成 釋出工作

開發環境一般是 Windows/mac,最後釋出到Linux。

沒有docker之前,使用jar包釋出,配上大量的shell腳本,然後各種配置,及其複雜。

有了docker之後,做好鏡像,開發打包部署上線,一套流程做完!

(2)快速完成 傳遞工作

傳統的傳遞工作,要給使用者提供各種安裝的幫助文檔,安裝程式,基礎環境安裝,依賴的中間件安裝,等等等等。

有了docker之後,能更快速的傳遞和部署。 給他一套鏡像,通過Docker指令,一鍵運作,啥都是好的。

那你看,docker 是不是,真香。

(3)更便捷的更新和擴縮容

使用了 Docker之後,我們項目打包為一個鏡像,部署應用就和搭積木一樣

擴充伺服器A,啟動一個容器就ok。

擴充伺服器B,啟動一個容器就ok。

如果要動态擴充,使用K8S這類分布式容器管理基礎設施,配上一個HPA 控制器元件,就能自動的完成動态擴容,動态縮容。

那你看,docker 是不是,真香。

(4)伺服器的性能可以被壓榨到極緻

Docker是核心級别的虛拟化,可以在一個實體機上可以運作很多的容器執行個體。

伺服器的性能可以被壓榨到極緻

那你看,docker 是不是,真香。

接下來,随着40歲老架構師一起,來穿透docker的原理和實操吧。

隻有先穿透docker,才能穿透K8S,最終穿透雲原生+大資料,實作你的技術自由。

Docker的曆史

2010年,幾個的年輕人在美國的舊金山成立了一家公司 dotcloud。dotcloud 是一個Paas平台的創業公司,從事LXC(Linux Container容器)有關的容器技術。

Linux Container容器是一種核心虛拟化技術,可以提供輕量級的虛拟化,以便隔離程序和資源。他們将自己的技術(容器化技術)命名就是 Docker。

Docker剛剛延生的時候,沒有引起行業的注意!

雖然獲得了創業孵化器(Y Combinator)的支援、也獲得過一些融資,但随着IT巨頭們(微軟、谷歌、亞馬遜等廠商)也進入PaaS憑他,dotCloud舉步維艱,眼看就活不下去!

2013年,dotCloud的創始人,28歲的Solomon Hykes做了一個艱難的決定,将dotCloud的核心引擎開源,這項核心引擎技術能夠将Linux容器中的應用程式、代碼打包,輕松的在伺服器之間進行遷移。

2013釋出了 Docker-compose 元件提供容器的編排工具。

2014年 Docker 釋出1.0版本,2015年Docker 提供 Docker-machine,支援 windows 平台。

docker火了。

這個基于LXC技術的核心管理引擎開源後,讓全世界的技術人員感到驚豔。

大家感歎這一切太友善了!!于是,越來越多的人發現docker的優點,使用他!

雖然,Docker 項目在開源社群大受追捧,同時也被業界诟病的是: Docker 公司對于 Docker 發展具有絕對的話語權,比如 Docker 公司推行了 libcontainer 難以被社群接受。

為了防止 Docker 這項開源技術被Docker 公司控制,幾個核心貢獻代碼的廠商諸如 Redhat,谷歌的倡導下,成立了 OCI 開源社群,制定了 OCI 開放容器标準,Open Container Initiative(OCI,開放容器标準)。

OCI 開源社群旨在于将 Docker 的發展權利回歸社群,當然反過來講,Docker 公司也希望更多的廠商安心貢獻代碼到Docker 項目,促進 Docker 項目的發展。

Docker 将自己容器格式和運作時 runC 捐給了 OCI,OCI 在此基礎上制定了 2 個标準:

運作時标準 Runtime Specification (runtime-spec)

鏡像标準 Image Specification (image-spec) :

于是通過OCI建立了 runc 項目,替代 libcontainer,這為開發者提供了除 Docker 之外的容器化實作的選擇。

OCI 社群提供了 runc 的維護,而 runc 是基于 OCI 規範的運作容器的工具。

換句話說,你可以通過 runc,提供自己的容器實作,而不需要依賴 Docker。

當然,Docker 的發行版底層也是用的 runc。在 Docker 主控端上執行 runc,你會發現它的大多數指令和 Docker 指令類似,感興趣的讀者可以自己實踐如何用 runc 啟動容器。

至2017年,Docker 項目轉移到 Moby 項目,基于 Moby 項目,Docker 提供了兩種發行版,Docker CE 和 Docker EE, Docker CE 就是目前大家普遍使用的版本,Docker EE 成為付費版本,提供了容器的編排,Service 等概念。

Docker 公司承諾 Docker 的發行版會基于 Moby 項目。這樣一來,通過 Moby 項目,你也可以自己打造一個定制化的容器引擎,而不會被 Docker 公司綁定。

Docker 的入門知識

從大家常用的Docker Engine開始說起。

Docker Engine

當人們說“Docker”時,他們通常是指 Docker Engine,它是一個用戶端 - 伺服器應用程式,

Docker 引擎由如下主要的元件構成:Docker 用戶端(Docker Client)、Docker 守護程序(Docker daemon)、containerd 以及 runc。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker 引擎

Docker Engine 從 CLI 中接受docker 指令,完成容器的管理:

例如使用 docker run 、docker ps 來列出正在運作的容器、

例如使用docker images 來列出鏡像,

等等。

Docker是一個Client-Server結構的系統,Docker守護程序運作在主機上,

Client通過Socket連接配接從用戶端通路Docker守護程序。Docker守護程序從用戶端接受指令,并按照指令,管理運作在主機上的容器。

  • 背景進行(dockerd)
  • REST API Server
  • CLI接口(docker)
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker Platform

  • Docker提供了一個開發,打包,運作app的平台
  • 把app和底層infrastructure隔離開來

其三層模型如圖:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

三層模型

到底什麼是docker:

到底什麼是docker:

  • docker是一個軟體,可以運作在window、linux、mac等各種作業系統上。
  • docker 是一個開源的應用容器引擎,基于Go 語言開發并遵從 Apache2.0 協定開源,項目代碼托管在github上進行維護
  • docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後釋出到任何流行的 Linux 機器上。
  • 容器是完全使用沙箱機制,互相之間不會有任何接口,更重要的是容器性能開銷極低。

什麼是容器?

什麼是容器?

  • 對軟體和其依賴的标準化打包
  • 應用之間互相隔離
  • 共享同一個OS Kernel
  • 可以運作在很多主流作業系統上

注:容器和虛拟機的差別在于容器是APP層面的隔離,而虛拟化是實體資源層面的隔離

容器解決了什麼問題?

  • 解決了開發和運維之間的沖突
  • 在開發和運維之間搭建了一個橋梁,是實作devops最佳解決方案

一個docker 容器,是一個運作時環境,可以簡單了解為程序運作的集裝箱。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

docker基本組成

docker主機(Host):安裝了Docker程式的機器(Docker直接安裝在作業系統之上);

docker倉庫(Registry):用來儲存各種打包好的軟體鏡像;倉庫分為公有倉庫和私有倉庫。(很類似 maven)

docker鏡像(Images):軟體打包好的鏡像;放在docker倉庫中;

docker容器(Container):鏡像啟動後的執行個體稱為一個容器;容器是獨立運作的一個或一組應用

Docker 包括三個基本概念:

  • 鏡像(Image):Docker 鏡像(Image),就相當于是一個 root 檔案系統。比如官方鏡像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系統的 root 檔案系統。
  • 容器(Container):鏡像(Image)和容器(Container)的關系,就像是面向對象程式設計中的類和執行個體一樣,鏡像是靜态的定義,容器是鏡像運作時的實體。容器可以被建立、啟動、停止、删除、暫停等。
  • 倉庫(Repository):倉庫可看成一個代碼控制中心,用來儲存鏡像。

Docker 使用用戶端-伺服器 (C/S) 架構模式,使用遠端API來管理和建立Docker容器。

Docker 容器通過 Docker 鏡像來建立。

概念 說明
Docker 鏡像(Images) Docker 鏡像是用于建立 Docker 容器的模闆,比如 Ubuntu 系統。
Docker 容器(Container) 容器是獨立運作的一個或一組應用,是鏡像運作時的實體。
Docker 用戶端(Client) Docker 用戶端通過指令行或者其他工具使用 Docker SDK (https://docs.docker.com/develop/sdk/) 與 Docker 的守護程序通信。
Docker 主機(Host) 一個實體或者虛拟的機器用于執行 Docker 守護程序和容器。
Docker Registry Docker 倉庫用來儲存鏡像,可以了解為代碼控制中的代碼倉庫。Docker Hub(https://hub.docker.com) 提供了龐大的鏡像集合供使用。一個 Docker Registry 中可以包含多個倉庫(Repository);每個倉庫可以包含多個标簽(Tag);每個标簽對應一個鏡像。通常,一個倉庫會包含同一個軟體不同版本的鏡像,而标簽就常用于對應該軟體的各個版本。我們可以通過 <倉庫名>:<标簽> 的格式來指定具體是這個軟體哪個版本的鏡像。如果不給出标簽,将以 latest 作為預設标簽。

Docker 與虛拟機有何差別

Docker 的誤解:Docker 是輕量級的虛拟機。

很多人将docker了解為, Docker 實作了類似于虛拟化的技術,能夠讓應用跑在一些輕量級的容器裡。這麼了解其實是錯誤的。

docker和kvm都是虛拟化技術,它們的主要差别:

1、Docker有着比虛拟機更少的抽象層

2、docker利用的是主控端的核心,VM需要的是Guest OS

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

二者的不同:

  • VM(VMware)在主控端器、主控端器作業系統的基礎上建立虛拟層、虛拟化的作業系統、虛拟化的倉庫,然後再安裝應用;
  • Container(Docker容器),在主控端器、主控端器作業系統上建立Docker引擎,在引擎的基礎上再安裝應用。

是以說,建立一個容器的時候,docker不需要像虛拟機一樣重新加載一個作業系統,避免引導。docker是利用主控端的作業系統,省略了這個複雜的過程,秒級!

虛拟機是加載Guest OS ,這是分鐘級别的

與傳統VM特性對比:

作為一種輕量級的虛拟化方式,Docker在運作應用上跟傳統的虛拟機方式相比具有顯著優勢:

  • Docker 容器很快,啟動和停止可以在秒級實作,這相比傳統的虛拟機方式要快得多。
  • Docker 容器對系統資源需求很少,一台主機上可以同時運作數千個Docker容器。
  • Docker 通過類似Git的操作來友善使用者擷取、分發和更新應用鏡像,指令簡明,學習成本較低。
  • Docker 通過Dockerfile配置檔案來支援靈活的自動化建立和部署機制,提高工作效率。
  • Docker 容器除了運作其中的應用之外,基本不消耗額外的系統資源,保證應用性能的同時,盡量減小系統開銷。
  • Docker 利用Linux系統上的多種防護機制實作了嚴格可靠的隔離。從1.3版本開始,Docker引入了安全選項和鏡像簽名機制,極大地提高了使用Docker的安全性。
特性 容器 虛拟機
啟動速度 秒級 分鐘級
硬碟使用 一般為MB 一般為GB
性能 接近原生 弱于原生
系統支援量 單機支援上千個容器 一般幾十個

docker與作業系統比較

docker是一種輕量級的虛拟化方式。與傳統作業系統技術的特性比較如下表:

特 性 容 器 虛 拟 機
啟動速度 秒級 分鐘級
性能 接近原生 較弱
記憶體代價 很小 較多
硬碟使用 一般為MB 一般為GB
運作密度 單機支援上千個容器 一般幾十個
隔離性 安全隔離 完全隔離
遷移性 優秀 一般

傳統的虛拟機方式提供的是相對封閉的隔離。

Docker利用Linux系統上的多種防護技術實作了嚴格的隔離可靠性,并且可以整合衆多安全工具。

從 1.3.0版本開始,docker重點改善了容器的安全控制和鏡像的安全機制, 極大提高了使用docker的安全性。

Docker 的安裝

安裝docker前置條件

當我們安裝 Docker 的時候,會涉及兩個主要元件:

  • Docker CLI:用戶端
  • Docker daemon:有時也被稱為“服務端”或者“引擎”

環境準備

硬體總體要求,可以參考尼恩的本地硬體情況:

1、硬體要求。

本文硬體總體要求如下表:

序号 硬體 要求
1 CPU 至少2核
2 記憶體 至少8G
3 硬碟 至少100G磁盤空間

2、本地虛拟機環境

軟體 版本
Win win10以上
virtual box 6以上
vagrant 2以上

docker+K8S學習環境非常複雜,尼恩搞這個 前前後後起碼折騰了一周,

其中,很多頭疼的工作,包括linux核心更新、磁盤擴容等等, 苦不堪言。

現在把這個環境,以虛拟機box鏡像的方式,導出來直接給大家,

大家一鍵導入後,直接享受docker 的實操,享受K8S的實操,可以說,爽到不要不要的。

以上軟體和 尼恩個人的虛拟機box鏡像,可以找尼恩擷取。

docker安裝的三種方式

安裝docker的三種方式

(1)離線安裝

(2)線上安裝

(3)用現成的

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

方式一 :離線安裝docker

這裡以 19.03.9 版本進行介紹, 其他版本是一樣的。

這種安裝方式,可以用于沒有 網際網路的 場景。

比如,很多公司,并不能直接上外網。

一、基礎環境

1、作業系統:CentOS 7.3

2、Docker版本:19.03.9 官方下載下傳位址https://download.docker.com/linux/static/stable/x86_64/

3、官方參考文檔:https://docs.docker.com/install/linux/docker-ce/binaries/#install-static-binaries

二、Docker安裝

1、下載下傳

wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz

注意:如果事先下載下傳好了可以忽略這一步

2、解壓

把壓縮檔案存在指定目錄下(如root),并進行解壓

tar -zxvf docker-19.03.9.tgz

cd root
[root@localhost ~]# tar -zxvf docker-19.03.6.tgz
docker/
docker/containerd
docker/docker
docker/ctr
docker/dockerd
docker/runc
docker/docker-proxy
docker/docker-init
docker/containerd-shim           

3、将解壓出來的docker檔案内容移動到 /usr/bin/ 目錄下

cp docker/* /usr/bin/

4、将docker注冊為service

cat /etc/systemd/system/docker.service

vi /etc/systemd/system/docker.service

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID

# Having non-zero Limit*s causes performance problems due to accounting overhead

# in the kernel. We recommend using cgroups to do container-local accounting.

LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
#TasksMax=infinity
TimeoutStartSec=0

# set delegate yes so that systemd does not reset the cgroups of docker containers

Delegate=yes

# kill only the docker process, not all processes in the cgroup

KillMode=process

# restart the docker process if it exits prematurely

Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target           

5、啟動

chmod +x /etc/systemd/system/docker.service  #添加檔案權限并啟動docker
systemctl daemon-reload               #重載unit配置檔案
systemctl start docker     #啟動Docker
systemctl enable docker.service       #設定開機自啟           
[root@localhost ~]# vi /etc/systemd/system/docker.service
[root@localhost ~]# chmod +x /etc/systemd/system/docker.service
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl start docker
[root@localhost ~]# systemctl enable docker.service
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /etc/systemd/system/docker.service.           

6、驗證

systemctl status docker #檢視Docker狀态
docker -v #檢視Docker版本
docker info           
[root@localhost ~]# systemctl status docker
 docker.service - Docker Application Container Engine
   Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2021-10-09 15:25:44 CST; 29s ago
     Docs: https://docs.docker.com
 Main PID: 1916 (dockerd)
   CGroup: /system.slice/docker.service
           ├─1916 /usr/bin/dockerd
           └─1927 containerd --config /var/run/docker/containerd/containerd.toml --log-level info

Oct 09 15:25:43 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:43.671407996+08:00" level=info msg="scheme \"unix\" not r...e=grpc
Oct 09 15:25:43 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:43.671440368+08:00" level=info msg="ccResolverWrapper: se...e=grpc
Oct 09 15:25:43 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:43.671462935+08:00" level=info msg="ClientConn switching ...e=grpc
Oct 09 15:25:43 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:43.750687781+08:00" level=info msg="Loading containers: start."
Oct 09 15:25:44 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:44.072960862+08:00" level=info msg="Default bridge (docke...dress"
Oct 09 15:25:44 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:44.153444071+08:00" level=info msg="Loading containers: done."
Oct 09 15:25:44 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:44.175249299+08:00" level=info msg="Docker daemon" commit...9.03.6
Oct 09 15:25:44 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:44.175337834+08:00" level=info msg="Daemon has completed ...ation"
Oct 09 15:25:44 localhost.localdomain systemd[1]: Started Docker Application Container Engine.
Oct 09 15:25:44 localhost.localdomain dockerd[1916]: time="2021-10-09T15:25:44.195084106+08:00" level=info msg="API listen on /var/ru....sock"
Hint: Some lines were ellipsized, use -l to show in full.
[root@localhost ~]# docker -v
Docker version 19.03.6, build 369ce74a3c
[root@localhost ~]# docker info           

方式二 :線上安裝docker

如果可以連接配接公網,建議線上安裝。

這裡注意 linux和 docker的版本。

尼恩安裝 docker 最新版本的時候,發現依賴了 Centos 8 以上的版本。

線上安裝docker步驟

  1. 更新yum
yum update           
  1. 安裝工具包
yum -y install yum-utils           
  1. 設定yum源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo           

以騰訊源為例

https://mirrors.cloud.tencent.com/docker-ce/linux/centos/docker-ce.repo           
  1. 安裝Docker-Ce(社群版)
yum install docker-ce           
  1. 檢視docker版本(用來确認是否安裝成功)
# 輸入 docker-v 後如果出現下面的内容則代表安裝成功
[root@VM-24-9-centos ~]# docker -v
Docker version 20.10.23, build 7155243           
  1. Docker鏡像加速(國内使用)
# 需要确定/etc下面是否有docker這個檔案夾,若沒有則需要使用下面的指令進行建立
mkdir -p /etc/docker

# 建立配置檔案daemon.json
vi /etc/docker/daemon.json

# 寫入以下内容
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com" # 可以替換為其他廠商的位址
    ]
}

# 重載一下配置
systemctl daemon-reload           
  1. 啟動Docker服務
systemctl start docker           
  1. 驗證docker是否可以使用
[root@VM-24-9-centos ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES           

方式三 :用現成的 (大大的省事)

docker+K8S 是一整套技術體系。

但是,docker+K8S學習環境非常複雜,尼恩搞這個 前前後後起碼折騰了一周。

其中,很多頭疼的工作,包括linux核心更新、磁盤擴容等等, 苦不堪言。

現在把這個環境,以虛拟機box鏡像的方式,導出來直接給大家,

大家一鍵導入後,直接享受docker 的實操,享受K8S的實操,

可以說,爽到不要不要的。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

以上環境和 尼恩個人的虛拟機box鏡像,可以找尼恩擷取。

Docker Container概述

什麼是Container 容器

  • 通過Image建立(copy)
  • 在Image layer之上建立一個container layer(可讀寫)
  • 類比面向對象:類和執行個體
  • Image負責app的存儲和分發,Container負責運作app
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

容器與鏡像的關系

  • 鏡像:鏡像是隻讀檔案,提供運作程式完整的軟硬體資源。
  • 容器:容器是鏡像的執行個體,由docker負責建立,容器之間彼此隔離。
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker本地容器相關的操作

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Container相關指令

  • 建立容器 docker run centos
  • 建立容器 docker run -it centos
  • 檢視活躍容器 docker ps
  • 查詢容器狀态 docker container ls -a
  • 移除容器 docker container rm + [container ID]
  • 移除鏡像 docker image rm + [image ID]
  • 顯示所有containerID docker container ls -aq
  • 移除所有的container docker rm $(docker container ls -aq)

建立容器

建立名為"centos6"的容器,并在容器内部和主控端中檢視容器中的程序資訊

docker run -itd -p 6080:80 -p 6022:22 docker.io/lemonbar/centos6-ssh:latest           

結果如下

[root@VM-4-17-centos ~]#    docker run -itd -p 80:80 -p 6022:22 docker.io/lemonbar/centos6-ssh:latest
Unable to find image 'lemonbar/centos6-ssh:latest' locally
latest: Pulling from lemonbar/centos6-ssh
a3ed95caeb02: Pull complete
f79eb1f22352: Pull complete
67c1aaa530c8: Pull complete
80447774eee7: Pull complete
6d67b3a80e5a: Pull complete
f1819e4b2f8f: Pull complete
09712b5b9acc: Pull complete
8bc987c5494f: Pull complete
c42b021d0ff2: Pull complete
Digest: sha256:093c2165b3c6fe05d5658343456f9b59bb7ecc690a7d3a112641c86083227dd1
Status: Downloaded newer image for lemonbar/centos6-ssh:latest
a4f1c9b8abcda78c8764cc285183dfa56cd1aa4ce6d111d4d9e77f3a57f3d5fc           

檢視活躍容器 docker ps

docker ps

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

檢視全部容器

查詢容器狀态

docker ps -a

docker container ls -a

兩個指令,效果差不多

停止容器

docker stop id

删除容器

docker rm id

檢視容器的程序資訊

docker top:檢視容器中運作的程序資訊,支援 ps 指令參數。

文法

docker top [OPTIONS] CONTAINER [ps OPTIONS]           

容器運作時不一定有/bin/bash終端來互動執行top指令,而且容器還不一定有top指令,

可以使用docker top來實作檢視container中正在運作的程序。

docker top 容器名稱           

尼恩的虛拟機中,存在容器zookeeper/ mysql,如果想檢視zookeeper/ mysql 容器内的運作程序資訊,

可以使用下述指令:

docker top zookeeper
docker top mysql           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

如何查找容器名稱?

很多的指令,用到容器名稱

如何查找容器名稱,可以使用下面的指令

[root@localhost ~]# docker ps --format "{{.Names}}"           

結果如下:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

docker最為常用的幾個指令

docker的守護程序檢視

systemctl status docker

docker 鏡像檢視

docker image ls

docker 容器檢視

docker ps

Docker Registry配置和檢視

cat /etc/docker/daemon.json

配置私有倉庫

cat>/etc/docker/daemon.json<<EOF

{
  "registry-mirrors":["http://10.24.2.30:5000","https://tnxkcso1.mirrors.aliyuncs.com"],

  "insecure-registries":["10.24.2.30:5000"]
}

EOF           

Docker容器進入的4種方式

在使用Docker建立了容器之後,大家比較關心的就是如何進入該容器了,

其實進入Docker容器有好幾多種方式,這裡我們就講一下常用的幾種進入Docker容器的方法。

進入Docker容器比較常見的幾種做法如下:

  • 使用docker attach
  • 使用SSH
  • 使用nsenter
  • 使用exec

方式1:使用docker attach進入Docker容器

Docker提供了attach指令來進入Docker容器。

接下來我們建立一個守護态的Docker容器,然後使用docker attach指令進入該容器。

sudo docker run -itd ubuntu:14.04 /bin/bash             

然後我們使用docker ps檢視到該容器資訊,接下來就使用docker attach進入該容器

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
docker attach c1437f4bd302             

可以看到我們已經進入到該容器中了。

但在,使用該指令有一個問題:

當多個視窗同時使用該指令進入該容器時,所有的視窗都會同步顯示。

如果有一個視窗阻塞了,那麼其他視窗也無法再進行操作。

因為這個原因,是以docker attach指令不太适合于生産環境,平時自己開發應用時可以使用該指令。

方式2:使用SSH進入Docker容器

在生産環境中排除了使用docker attach指令進入容器之後,相信大家第一個想到的就是ssh。

在鏡像(或容器)中安裝SSH Server,這樣就能保證多人進入。

容器且互相之間不受幹擾了,相信大家在目前的生産環境中(沒有使用Docker的情況)也是這樣做的。

但是使用了Docker容器之後不建議使用ssh進入到Docker容器内。

方式3:使用nsenter進入Docker容器

在上面兩種方式都不适合的情況下,還有一種比較友善的方法,即使用nsenter進入Docker容器。

1、什麼是nsenter?

nsenter指令是一個可以在指定程序的指令空間下運作指定程式的指令。它位于util-linux包中。

util-linux 是一個開放源碼的軟體包,是一個對任何 Linux 系統的基本工具套件。含有一些标準 Unix 工具,如 login。

util-linux 軟體包包含許多工具。其中比較重要的是加載、解除安裝、格式化、分區和管理硬碟驅動器,打開 tty 端口和得到核心消息。

nsenter用途 ?

一個最典型的用途就是進入容器的網絡指令空間。相當多的容器為了輕量級,是不包含較為基礎的指令的,比如說ip address,ping,telnet,ss,tcpdump等等指令,這就給調試容器網絡帶來相當大的困擾:隻能通過docker inspect ContainerID指令擷取到容器IP,以及無法測試和其他網絡的連通性。這時就可以使用nsenter指令僅進入該容器的網絡命名空間,使用主控端的指令調試容器網絡。

在了解了什麼是nsenter之後,系統預設将我們需要的nsenter安裝到主機中, nsenter --help 檢視幫助

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

nsenter --help 檢視幫助

$ nsenter --help

nsenter [options] [program [arguments]]

options:
-t, --target pid:指定被進入命名空間的目标程序的pid
-m, --mount[=file]:進入mount指令空間。如果指定了file,則進入file的指令空間
-u, --uts[=file]:進入uts指令空間。如果指定了file,則進入file的指令空間
-i, --ipc[=file]:進入ipc指令空間。如果指定了file,則進入file的指令空間
-n, --net[=file]:進入net指令空間。如果指定了file,則進入file的指令空間
-p, --pid[=file]:進入pid指令空間。如果指定了file,則進入file的指令空間
-U, --user[=file]:進入user指令空間。如果指定了file,則進入file的指令空間
-G, --setgid gid:設定運作程式的gid
-S, --setuid uid:設定運作程式的uid
-r, --root[=directory]:設定根目錄
-w, --wd[=directory]:設定工作目錄

如果沒有給出program,則預設執行$SHELL。           

2、nsenter安裝

如果沒有安裝的話,按下面步驟安裝即可(注意是主機而非容器或鏡像)

具體的安裝指令如下:

wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz
tar -xzvf util-linux-2.24.tar.gz
cd util-linux-2.24/
./configure --without-ncurses
make nsenter
sudo cp nsenter /usr/local/bin           

3、nsenter的 使用

nsenter可以通路另一個程序的名稱空間。

是以為了連接配接到某個容器我們還需要擷取該容器的第一個程序的PID。

怎麼辦呢 ? 可以使用docker inspect指令來拿到該 程序的 PID。

docker inspect 指令使用如下:

sudo docker inspect --help           

inspect指令可以分層級顯示一個鏡像或容器的資訊。

比如我們目前有一個正在運作的容器

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

可以使用docker inspect來檢視該容器的詳細資訊。

sudo docker inspect c1437f4bd302           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

由其該資訊非常多,此處隻截取了其中一部分進行展示。如果要顯示該容器第一個進行的PID可以使用如下方式

sudo docker inspect -f {{.State.Pid}}    c1437f4bd302           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

在拿到該程序PID之後我們就可以使用nsenter指令通路該容器了。

sudo nsenter --target 22299 --mount --uts --ipc --net --pid           

其中的 22299 即剛才拿到的程序的PID

輸入該指令便進入到容器中

$ nsenter --target 上面查到的程序id --mount --uts --ipc --net --pid           

解釋nsenter指令中程序id之後的參數的含義:

–mount參數是進去到mount namespace中 (檔案系統)

–uts參數是進入到uts namespace中 (主機名與域名)

–ipc參數是進入到System V IPC namaspace中 (信号量、消息隊列和共享内容)

–net參數是進入到network namespace中 (網絡裝置、網絡棧、端口)

–pid參數是進入到pid namespace中 (程序編号)

–user參數是進入到user namespace中 (使用者和使用者組)

看看下面的例子,進入到容器的 network namespace中 ,看看IP位址,是不是變了。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

docker隔離應用應用涉及到的六大名稱空間

1、pid 命名空間(程序ID)

不同使用者的程序就是通過 pid 命名空間隔離開的,且不同命名空間中可以有相同 pid。

所有的 LXC (Linux 容器)程序在 Docker 中的父程序為 Docker 程序,每個 LXC 程序具有不同的命名空間。

同時由于允許嵌套,是以可以很友善的實作嵌套的 Docker 容器。

2、net 命名空間(網絡)

有了 pid 命名空間,每個命名空間中的 pid 能夠互相隔離,但是網絡端口還是共享 host 的端口。

網絡隔離是通過 net 命名空間實作的, 每個 net 命名空間有獨立的 網絡裝置,IP 位址,路由表,/proc/net 目錄。這樣每個容器的網絡就能隔離開來。

Docker 預設采用 veth 的方式,将容器中的虛拟網卡同 host 上的一 個Docker 網橋 docker0 連接配接在一起。

3、ipc 命名空間(程序間通信)

容器中程序互動還是采用了 Linux 常見的程序間互動方法(interprocess communication - IPC), 包括信号量、消息隊列和共享記憶體等。

然而同 VM 不同的是,容器的程序間互動實際上還是 host 上具有相同 pid 命名空間中的程序間互動,是以需要在 IPC 資源申請時加入命名空間資訊,每個 IPC 資源有一個唯一的 32 位 id。

4、mnt 命名空間(挂載檔案系統)

類似 chroot,将一個程序放到一個特定的目錄執行。

mnt 命名空間允許不同命名空間的程序看到的檔案結構不同,這樣每個命名空間 中的程序所看到的檔案目錄就被隔離開了。

同 chroot 不同,每個命名空間中的容器在 /proc/mounts 的資訊隻包含所在命名空間的 mount point。

5、UTS 命名空間(主機名/域名)

UTS("UNIX Time-sharing System") 命名空間允許每個容器擁有獨立的 hostname 和 domain name, 使其在網絡上可以被視作一個獨立的節點而非 主機上的一個程序。

6、user 命名空間(使用者)

每個容器可以有不同的使用者群組 id, 也就是說可以在容器内用容器内部的使用者執行程式而非主機上的使用者。

nsenter檢視docker的連接配接

由于使用DOCKER的時候,ESTABLISHED連接配接不會出現在netstat中,在運作中的docker容器中列出打開的套接字的方法

檢視連接配接:

sudo docker inspect -f {{.State.Pid}}    e9eaef999da9

sudo nsenter -t  3473  -n netstat | grep ESTABLISHED              
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

方式4:使用docker exec進入Docker容器

除了上面幾種做法之外,docker在1.3.X版本之後還提供了一個新的指令exec用于進入容器,這種方式相對更簡單一些,下面我們來看一下該指令的使用:

sudo docker exec --help           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

接下來我們使用該指令進入一個已經在運作的容器

sudo docker ps  
sudo docker exec -it 容器id  /bin/bash            
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

在容器内部和主控端中檢視容器中的程序資訊

進入一個名稱為 rmqbroker-ha-b 的容器,檢視程序資訊

docker exec -it rmqbroker-ha-b  /bin/bash   ps -ef           

結果如下:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

我們可以使用docker exec指令進入容器PID名空間,并執行應用。

通過ps -ef指令,可以看到每個 rmqbroker-ha-b 容器都包含一個PID為1的程序,

容器的啟動程序是 "sh mqbroker -c /opt/rocketmq.....",具有特殊意義。

利用docker top指令,可以讓我們從主控端作業系統中看到容器的程序資訊。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

"sh mqbroker -c /opt/rocketmq....." 這個指令的程序,在 容器裡邊是1 ,在容器外部 3473

檢視其父程序資訊

ps -ef | grep 3401

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
root        3401       1  0 12:06 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id e9eaef999da9183b9be0b3239881bc6b9c2070f13057c322dfed3d072820e962 -address /run/containerd/containerd.sock
3000        3473    3401  0 12:06 ?        00:00:00 sh mqbroker -c /opt/rocketmq-4.6.0/conf/broker.conf autoCreateTopicEnable=true &
root       26491   24084  0 16:16 pts/1    00:00:00 grep --color=auto 3401           

我們使用docker run 啟用一個容器時,docker 會給每個容器都啟動一個containerd-shim-runc-v2 父程序,這個程序又啟動了一個ttrpc server(類似grpc/httpserver), containerd 通過 ttrpc和containerd-shim-runc-v2 通信來管理容器

從父親程序可以看到:容器的本質是程序。

containerd-shim-runc-v2 後面的參數:namespace用來做命名空間隔離,cgroup用來做資源限制。

containerd-shim-runc-v2 程序很特殊,它們跑在一些特定的namespace和cgroup下。

站在這些程序的角度看,它們會以為自己跑在一個獨立的機器上,看不到其他程序,也看不到其他檔案。

這是其實是作業系統為它虛拟出來的一個獨立的、隔離的環境,是假的。

檢視子程序資訊

[root@VM-4-17-centos ~]#  ps aux | grep 27880           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

總計三個指令

docker top  rmqbroker-ha-b  #檢視容器程序

ps -ef | grep 3401 #檢視父程序
ps aux | grep 3473  #檢視子程序(容器)           

Docker本地鏡像載入與載出

兩種辦法

  • 儲存鏡像(儲存鏡像載入後獲得跟原鏡像id相同的鏡像)
  • 儲存容器(儲存容器載入後獲得跟原鏡像id不同的鏡像)

拉取鏡像

通過指令可以從鏡像倉庫中拉取鏡像,預設從Docker Hub 擷取。

指令格式:

docker image pull <repository>:<tag>

docker image pull   rancher/rke-tools:v0.1.52

[rancher/rke-tools:v0.1.52           

儲存鏡像

  • docker save 鏡像id -o /home/mysql.tar
  • docker save 鏡像id > /home/mysql.tar
docker save  docker.io/rancher/rancher-agent   -o /home/rancher-agent .tar

docker save  f29ece87a195  -o /home/rancher-agent.tar

docker save  docker.io/rancher/rke-tools   -o /home/rke-tools-v0.1.52.tar           

載入鏡像

  • docker load -i mysql.tar
docker load -i /usr/local/rancher-v2.3.5.tar

docker load -i /usr/local/rancher-agent.tar

docker  inspect  f29ece87a1954772accb8a2332ee8c3fe460697e3f102ffbdc76eb9bc4f4f1d0

docker load -i /usr/local/rke-tools-v0.1.52.tar           
docker load -i mysql.tar

[root@localhost ~]# docker load -i /usr/local/rancher-v2.3.5.tar
43c67172d1d1: Loading layer [==================================================>]  65.57MB/65.57MB
21ec61b65b20: Loading layer [==================================================>]  991.2kB/991.2kB
1d0dfb259f6a: Loading layer [==================================================>]  15.87kB/15.87kB
f55aa0bd26b8: Loading layer [==================================================>]  3.072kB/3.072kB
e0af200d6950: Loading layer [==================================================>]  126.1MB/126.1MB
088ed892f9ad: Loading layer [==================================================>]  6.656kB/6.656kB
6aa3142b4130: Loading layer [==================================================>]   34.5MB/34.5MB
f4e84c05ab29: Loading layer [==================================================>]  70.41MB/70.41MB
11a6e4332b53: Loading layer [==================================================>]  224.8MB/224.8MB
46d1ac556da7: Loading layer [==================================================>]  3.072kB/3.072kB
0f8b224a5802: Loading layer [==================================================>]  57.87MB/57.87MB
519eba7d586a: Loading layer [==================================================>]  99.58MB/99.58MB
3f8bb7c0c150: Loading layer [==================================================>]  4.608kB/4.608kB
c22c9a5a8211: Loading layer [==================================================>]  3.072kB/3.072kB
Loaded image: rancher/rancher:v2.3.5           

打個tag

docker tag  f29ece87a1954772accb8a2332ee8c3fe460697e3f102ffbdc76eb9bc4f4f1d0 rancher/rancher-agent:v2.3.5

docker tag  f29ece87a195   172.18.8.104/rancher/rancher-agent:v2.3.5

docker tag 6e421b8753a2  172.18.8.104/rancher/rke-tools:v0.1.52 

83fe4871cf67           
docker rmi image_name

docker rmi  -f  172.18.8.104/rancher/coredns-coredns:1.6.5 

docker rmi   -f   172.18.8.104/rancher/coredns-coredns:v3.4.3-rancher1

docker rmi hub.doge.net/ubuntu:latest           

儲存鏡像

  • docker export 鏡像id -o /home/mysql-export.tar
  • docker save 鏡像tag -o /home/mysql-export.tar

載入鏡像

  • docker import mysql-export.tar

Harbor私有鏡像倉庫

Harbor (港口,港灣)是一個用于存儲和分發Docker鏡像的企業級Registry伺服器。

除了Harbor這個私有鏡像倉庫之外,還有Docker官方提供的Registry。

相對Registry,Harbor具有很多優勢:

  1. 提供分層傳輸機制,優化網絡傳輸 Docker鏡像是是分層的,而如果每次傳輸都使用全量檔案(是以用FTP的方式并不适合),顯然不經濟。必須提供識别分層傳輸的機制,以層的UUID為辨別,确定傳輸的對象。
  2. 提供WEB界面,優化使用者體驗 隻用鏡像的名字來進行上傳下載下傳顯然很不友善,需要有一個使用者界面可以支援登陸、搜尋功能,包括區分公有、私有鏡像。
  3. 支援水準擴充叢集 當有使用者對鏡像的上傳下載下傳操作集中在某伺服器,需要對相應的通路壓力作分解。
  4. 良好的安全機制 企業中的開發團隊有很多不同的職位,對于不同的職位人員,配置設定不同的權限,具有更好的安全性。

Harbor安裝

harbor是用過docker-compose 編排的。

是以 安裝的過程中,會檢查docker、docker-compose 程序,確定提前啟動。

這些,在咱們的虛拟機裡邊,docker、docker-compose 程序已經預裝好了的。

檢視docker是否安裝成功;

[root@centos1 ~]# docker -v
Docker version 20.10.23, build 7155243           

檢視docker-compose是否安裝成功;

docker-compose -version

[root@centos1 ~]# docker-compose -version
docker-compose version 1.25.1, build a82fef07           

接下來,開始安裝harbor

1、下載下傳 Harbor的壓縮包;

https://github.com/goharbor/harbor/releases           

咱們用這個包: harbor-offline-installer-v2.3.2.tgz

咱們的學習網盤當中,尼恩已經上傳了哈

2、上傳壓縮包到虛拟機,并解壓;

[root@centos1 ~]# cd /usr/local/
[root@centos1 local]# mkdir harber
[root@centos1 local]# cd harber/

tar -zxvf harbor-offline-installer-v2.3.2.tgz           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

3、建立harbor通路域名證書

OpenSSL是一個強大的安全套接字層密碼庫,Apache使用它加密HTTPS,OpenSSH使用它加密SSH,但是,你不應該隻将其作為一個庫來使用,它還是一個多用途的、跨平台的密碼工具。

參考:

Harbor docs | Configure HTTPS Access to Harbor (goharbor.io):https://goharbor.io/docs/2.6.0/install-config/configure-https/

mkdir -p /usr/local/harbor/ssl && cd /usr/local/harbor/ssl
openssl genrsa -out tls.key 4096
      
 openssl req -x509 -new -nodes -sha512 -days 3650 \
 -subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=cdh1" \
 -key tls.key \
 -out tls.cert           
  • 第一步建立ssl檔案夾用來存儲證書,
  • 第二步生成key (私鑰),
  • 最後一步使用生成的key自簽證書。自簽證書包含公鑰

days後面是你自簽證書的有效時間可以自行修改。

'CN='後面就寫你自己的IP位址或者你自己的域名。

生成完成之後顯示如下

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

4、配置harbor

修改Harbor的配置(harbor.yml);

修改主機位址:hostname: 192.168.56.121;

修改端口(預設端口80):port: 85;

cd /usr/local/harbor/harbor    #進入到harbor目錄
cp   harbor.yml.tmpl    harbor.yml
vim harbor.yml   #編輯harbor的配置檔案

#修改以下内容
hostname = 192.168.56.121  #修改harbor的啟動ip,這裡需要依據系統ip設定
port: 85 #harbor的端口,有兩個端口,http協定(80)和https協定(443)
harbor_admin_password = 123456   #修改harbor的admin使用者的密碼
data_volume: /harbor/data #修改harbor存儲位置           

故harbor.yml中certificate填寫為如上tls.cert的檔案目錄位址:/usr/local/harbor/ssl/tls.cert

故harbor.yml中private_key填寫為如上tls.key的檔案目錄位址:/usr/local/harbor/ssl/tls.key

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

5、./prepare 準備

啟動之前需要使用./prepare指令進行一些預置工作,下載下傳相關依賴;

此時需要開啟docker服務,不然會報錯;

這個過程可能有點長,需要耐心等待;

./prepare           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

6、./install.sh

準備工作完成後,使用./install.sh進行Harbor的安裝;

這個過程會持續一段時間,耐心等待;

./install.sh           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

通路

http://cdh1:85/

登入

預設登入名:admin;

預設登入密碼:Harbor12345; 被尼恩改成了 12345

具體可以檢視harbor.yml;

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

7、停止或者重新開機 Harbor

cd /usr/local/harber/harbor/

docker-compose up -d 啟動
docker-compose start 啟動
docker-compose stop 停止
docker-compose restart 重新啟動           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

修改docker配置檔案,使docker支援harbor

編輯客戶機/etc/docker/daemon.json檔案

{"insecure-registries":["192.168.56.121:85"]}

重新開機客戶機docker服務

systemctl restart docker #或者(service docker restart)

[root@centos1 harbor]# cat  /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://bjtzu1jb.mirror.aliyuncs.com",
    "http://f1361db2.m.daocloud.io",
    "https://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://reg-mirror.qiniu.com",
    "https://dockerhub.azk8s.cn",
    "https://registry.docker-cn.com"
  ],
  "insecure-registries":["192.168.56.121:85"]
}           
[root@centos1 ssh]# cat /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://bjtzu1jb.mirror.aliyuncs.com",
    "http://f1361db2.m.daocloud.io",
    "https://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://reg-mirror.qiniu.com",
    "https://dockerhub.azk8s.cn",
    "https://registry.docker-cn.com",
   "https://cdh1"
  ]
}           

Harbor使用

可以 嘗試上傳鏡像到 Harbor。

如果是另一台機器,需要添加本地hosts

echo '192.168.56.121 cdh' >> /etc/hosts           

因為我們的證書是自簽的是不受到其他機器信任的,是以我們要在 harber的用戶端機器上放上我們的證書,才能拉取鏡像。

使用下面的代碼,在要使用 harber的用戶端機器 上儲存證書,其中 cdh1 部分使用你自己的域名或者是IP位址。

mkdir -p /etc/docker/certs.d/cdh1
scp 192.168.56.122:/usr/local/harbor/ssl/tls.cert /etc/docker/certs.d/cdh1/           

本地docker 登入的話

mkdir -p /etc/docker/certs.d/cdh1
cp /usr/local/harbor/ssl/tls.cert /etc/docker/certs.d/cdh1/ca.crt

mkdir -p /etc/docker/certs.d/192.168.56.121           

然後使用如下語句登入harbor,如果沒有使用80端口一定要用IP位址加上你的端口号,冒号之後填端口号,檢視自己harbor.yml裡http或https裡的端口詳情。

docker login cdh1           

報錯 證書問題, 證書不含SAN

什麼是含有SAN的證書

docker 的新版本使用golang 1.15+版本上,老的x509 證書不行了

SAN(Subject Alternative Name) 是 SSL 标準 x509 中定義的一個擴充。使用了 SAN 字段的 SSL 證書,可以擴充此證書支援的域名,使得一個證書可以支援多個不同域名的解析。

subjectAltName 在 RFC 5280 4.2.1.6.中提供了詳細的說明,subjectAltName 是 X.509 version 3 的一個擴充項,該擴充項用于标記和界定證書持有者的身份。

在 X.509 格式的證書中,一般使用 Issuer 項标記證書的頒發者資訊,該項必須是一個非空的 Distinguished Name 名稱。除此之外還可以使用擴充項 issuerAltName 來标記頒發者的其他名稱,這是一個非關鍵的擴充項。

對于證書持有者,一般使用 Subject 項标記,并使用 subjectAltName 擴充項提供更詳細的持有者身份資訊。 subjectAltName 全稱為 Subject Alternative Name,縮寫為 SAN。它可以包括一個或者多個的電子郵件位址,域名,IP位址和 URI 等,詳細定義如下:

SubjectAltName ::= GeneralNames
   GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName

   GeneralName ::= CHOICE {
        otherName                       [0]     OtherName,
        rfc822Name                      [1]     IA5String,
        dNSName                         [2]     IA5String,
        x400Address                     [3]     ORAddress,
        directoryName                   [4]     Name,
        ediPartyName                    [5]     EDIPartyName,
        uniformResourceIdentifier       [6]     IA5String,
        iPAddress                       [7]     OCTET STRING,
        registeredID                    [8]     OBJECT IDENTIFIER 
    }           

當頒發的證書不存在 Subject 項的時候,證書必須包含擴充項 subjectAltName,并且标記為關鍵(critical)的。當頒發的證書存在 Subject 項的時候,必須将擴充項 subjectAltName 标記為非關鍵(no-critical)的。注意:用于頒發證書的 CA 證書是必須包含 Subject 項的。

根據 RFC 6125 中的規定,當一個網站使用證書标記自己的身份時,如果證書中包含 subjectAltName,在識别證書持有者時會忽略 Subject 子項,而是通過 subjectAltName 來識别證書持有者。

在早期頒發的證書中一般通過 Subject 的 CommonName 來識别持有者的身份,不包含 subjectAltName 擴充項。

這會導緻最新版本的浏覽器Chrome、Firefox 等在通過 HTTPS 通路 web 網站時,觸發 NET::ERR_CERT_COMMON_NAME_INVALID 錯誤。

SSL證書格式

證書主要的檔案類型和協定有:PEM、DER、PFX、JKS、KDB、CER、KEY、CSR、CRT、CRL 、OCSP、SCEP等。

1. KEY

一般指PEM格式的私鑰檔案。

2. CRT

證書檔案。可以是PEM格式。

3. PEM

PEM格式的證書檔案(*.pem)由Base64編碼的二進制内容和開頭行(-----BEGIN CERTIFICATE-----)、結束行(-----END CERTIFICATE-----)組成,支援使用notepad++等文本編輯器打開。對于CER、CRT格式的證書,您可通過直接修改證書檔案擴充名的方式将其轉換成PEM格式。例如,将server.crt證書檔案直接重命名為server.pem。

4. CSR

證書請求檔案(Certificate Signing Request)。生成 X509 數字證書前,一般先由使用者送出證書申請檔案,然後由 CA 來簽發證書。大緻過程如下(X509 證書申請的格式标準為 pkcs10 和 rfc2314):

  1. 使用者生成自己的公私鑰對;
  2. 構造自己的證書申請檔案,符合 PKCS10 标準。該檔案主要包括了使用者資訊、公鑰以及一些可選的屬性資訊,并用自己的私鑰給該内容簽名;
  3. 使用者将證書申請檔案送出給 CA;
  4. CA 驗證簽名,提取使用者資訊,并加上其他資訊(比如頒發者等資訊),用 CA 的私鑰簽發數字證書;
  5. 說明:數字證書(如x.509)是将使用者(或其他實體)身份與公鑰綁定的資訊載體。一個合法的數字證書不僅要符合 X509 格式規範,還必須有 CA 的簽名。使用者不僅有自己的數字證書,還必須有對應的私鑰。X509v3 數字證書主要包含的内容有:證書版本、證書序列号、簽名算法、頒發者資訊、有效時間、持有者資訊、公鑰資訊、頒發者 ID、持有者 ID 和擴充項。

5. DER

辨識編碼規則 (DER) 可包含所有私鑰、公鑰和證書。它是大多數浏覽器的預設格式,并按 ASN1 DER 格式存儲。它是無報頭的 - PEM 是用文本報頭包圍的 DER。

6. PFX

公鑰加密标準 12 (PKCS12) 可包含所有私鑰、公鑰和證書。其以二進制格式存儲,也稱為 PFX 檔案。通常可以将Apache/OpenSSL使用的“KEY檔案 + CRT檔案”格式合并轉換為标準的PFX檔案,你可以将PFX檔案格式導入到微軟IIS 5/6、微軟ISA、微軟Exchange Server等軟體。轉換時需要輸入PFX檔案的加密密碼。

7. JKS

通常可以将Apache/OpenSSL使用的“KEY檔案 + CRT檔案”格式”轉換為标準的Java Key Store(JKS)檔案。JKS檔案格式被廣泛的應用在基于JAVA的WEB伺服器、應用伺服器、中間件。你可以将JKS檔案導入到TOMCAT、 WEBLOGIC 等軟體。

8. KDB

通常可以将Apache/OpenSSL使用的“KEY檔案 + CRT檔案”格式轉換為标準的IBM KDB檔案。KDB檔案格式被廣泛的應用在IBM的WEB伺服器、應用伺服器、中間件。你可以将KDB檔案導入到IBM HTTP Server、IBM Websphere 等軟體。

9. OCSP

線上證書狀态協定(OCSP,Online Certificate Status Protocol,rfc2560)用于實時表明證書狀态。OCSP 用戶端通過查詢 OCSP 服務來确定一個證書的狀态,可以提供給使用者一個或多個數字證書的有效性資料,它建立一個可實時響應的機制,讓使用者可以實時确認每一張證書的有效性,解決由CRL引發的安全問題。。OCSP 可以通過 HTTP協定來實作。rfc2560 定義了 OCSP 用戶端和服務端的消息格式。

10. CER

一般指使用DER格式的證書。

11. CRL

證書吊銷清單 (Certification Revocation List) 是一種包含撤銷的證書清單的簽名資料結構。CRL 是證書撤銷狀态的公布形式,CRL 就像信用卡的黑名單,用于公布某些數字證書不再有效。CRL 是一種離線的證書狀态資訊。它以一定的周期進行更新。CRL 可以分為完全 CRL和增量 CRL。在完全 CRL 中包含了所有的被撤銷證書資訊,增量 CRL 由一系列的 CRL 來表明被撤銷的證書資訊,它每次釋出的 CRL 是對前面釋出 CRL 的增量擴充。基本的 CRL 資訊有:被撤銷證書序列号、撤銷時間、撤銷原因、簽名者以及 CRL 簽名等資訊。基于 CRL 的驗證是一種不嚴格的證書認證。CRL 能證明在 CRL 中被撤銷的證書是無效的。但是,它不能給出不在 CRL 中的證書的狀态。如果執行嚴格的認證,需要采用線上方式進行認證,即 OCSP 認證。一般是由CA簽名的一組電子文檔,包括了被廢除證書的唯一辨別(證書序列号),CRL用來列出已經過期或廢除的數字證書。它每隔一段時間就會更新,是以必須定期下載下傳該清單,才會取得最新資訊。

12. SCEP

簡單證書注冊協定。基于檔案的證書登記方式需要從您的本地計算機将文本檔案複制和粘貼到證書釋出中心,和從證書釋出中心複制和粘貼到您的本地計算機。 SCEP可以自動處理這個過程但是CRLs仍然需要手工的在本地計算機和CA釋出中心之間進行複制和粘貼。

13. PKCS7

加密消息文法(pkcs7),是各種消息存放的格式标準。這些消息包括:資料、簽名資料、數字信封、簽名數字信封、摘要資料和加密資料。

14. PKCS12

– pkcs12 (個人數字證書标準)用于存放使用者證書、crl、使用者私鑰以及證書鍊。pkcs12 中的私鑰是加密存放的。

生成含有SAN的證書

  • 使用OPENssl指令行來生成KEY+CSR2個檔案,
  • 使用KEYTOOL來生成JKS和CSR檔案

1、生成CA憑證私鑰

openssl genrsa -out ca.key 4096

[root@docker-compose-harbor CA]# openssl genrsa -out ca.key 4096
Generating RSA private key, 4096 bit long modulus
...............................................................++
............................................................++
e is 65537 (0x10001)
[root@docker-compose-harbor CA]#
[root@docker-compose-harbor CA]#
[root@docker-compose-harbor CA]# ls
ca.key
[root@docker-compose-harbor CA]#           

2、生成CA憑證

openssl req -x509 -new -nodes -sha512 -days 3650 \
-subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=cdh1" \
-key ca.key \
-out ca.crt           

自簽名的證書,不被浏覽器信任,适合内部或者測試使用。

3、生成伺服器證書

生成私鑰

[root@docker-compose-harbor CA]# openssl genrsa -out cdh1.key 4096
Generating RSA private key, 4096 bit long modulus
.........................................................................................................................++
...........................................................................................................................................................................................................++
e is 65537 (0x10001)
[root@docker-compose-harbor CA]#
[root@docker-compose-harbor CA]# ll -h
total 12K
-rw-r--r--. 1 root root 3.2K Jul 20 04:52 cdh1.key
-rw-r--r--. 1 root root 2.0K Jul 20 04:51 ca.crt
-rw-r--r--. 1 root root 3.2K Jul 20 04:48 ca.key
[root@docker-compose-harbor CA]#           

生成證書簽名請求(CSR)

制作過程中,系統會産生2個密鑰,一個是私鑰,存放在伺服器上,一個是CSR檔案公鑰,需要ca簽名。

openssl req -sha512 -new \
-subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=cdh1" \
-key cdh1.key \
-out cdh1.csr           

需要依次輸入國家,地區,城市,組織,組織機關,Common Name和Email。

其中Common Name,可以寫自己的名字或者域名,如果要支援https,Common Name應該與域名保持一緻,否則會引起浏覽器警告。

可以将證書發送給證書頒發機構(CA),CA驗證過請求者的身份之後,會出具簽名證書,需要花錢。

另外,如果隻是内部或者測試需求,也可以使用OpenSSL實作自簽名。

執行過程

[root@docker-compose-harbor CA]# openssl req -sha512 -new \
> -subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=cdh1" \
> -key cdh1.key \
> -out cdh1.csr

[root@docker-compose-harbor CA]# ll -h
total 20K
-rw-r--r--. 1 root root 1.7K Jul 20 04:59 cdh1.csr
-rw-r--r--. 1 root root 3.2K Jul 20 04:52 cdh1.key
-rw-r--r--. 1 root root 2.0K Jul 20 04:51 ca.crt
-rw-r--r--. 1 root root 3.2K Jul 20 04:48 ca.key
[root@docker-compose-harbor CA]#
生成一個x509 v3擴充檔案
cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = IP:cdh1
EOF           

4、使用該v3.ext檔案為Harbor主機生成證書cdh1.crt

openssl x509 -req -sha512 -days 3650 \
-extfile v3.ext \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-in cdh1.csr \
-out cdh1.crt           
[root@docker-compose-harbor CA]# openssl x509 -req -sha512 -days 3650 \
> -extfile v3.ext \
> -CA ca.crt -CAkey ca.key -CAcreateserial \
> -in cdh1.csr \
> -out cdh1.crt
Signature ok
subject=/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=cdh1
Getting CA Private Key
[root@docker-compose-harbor CA]#           

當服務端向 CA 機構申請證書的時候,CA 簽發證書的過程:

  • 首先 CA 會把持有者的公鑰、用途、頒發者、有效時間等資訊打成一個包,然後對這些資訊進行 Hash 計算,得到一個 Hash 值;
  • 然後 CA 會使用自己的私鑰将該 Hash 值加密,生成 Certificate Signature,也就是 CA 對證書做了簽名;
  • 最後将 Certificate Signature 添加在檔案證書上,形成數字證書;

5、轉換cdh1.crt為cdh1.cert,供Docker使用

轉換證書格式

Docker守護程式将.crt檔案解釋為CA憑證,并将.cert檔案解釋為用戶端證書

openssl x509 -inform PEM -in cdh1.crt -out cdh1.cert           

修改harbor.yml檔案key路徑

修改對應的證書位址:

  • cdh1.cert 的位址
  • cdh1.key 的位址

故harbor.yml中certificate填寫為如上cdh1.cert的檔案目錄位址:/usr/local/harbor/ssl/cdh1.cert

故harbor.yml中private_key填寫為如上cdh1.key的檔案目錄位址:/usr/local/harbor/ssl/cdh1.key

[root@docker-compose-harbor harbor]# egrep -v "^$|^#" harbor.yml |head -10
hostname: cdh1
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
port: 80
https:
# https port for harbor, default is 443
port: 443
# The path of cert and key files for nginx
certificate: /opt/CA/harbor/cert/cdh1.crt
private_key: /opt/CA/harbor/cert/cdh1.key
[root@docker-compose-harbor harbor]#           

6、運作prepare腳本以啟用HTTPS

./prepare

[root@docker-compose-harbor harbor]# ./prepare
prepare base dir is set to /opt/harbor/harbor
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf

Clean up the input dir
[root@docker-compose-harbor harbor]#           

7、運作install.sh腳本來啟動harbor

./install.sh

[root@docker-compose-harbor harbor]# ./install.sh 
[Step 0]: checking if docker is installed ...
Note: docker version: 20.10.17
[Step 1]: checking docker-compose is installed ...
Not           

證書複制到 docker 并且啟動後登入

cp /usr/local/harbor/ssl/cdh1.cert /etc/docker/certs.d/cdh1/ca.crt

systemctl daemon-reload && systemctl restart docker           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

hostname push失敗

問題:

push本地鏡像到 harbor私服時,push 到 docker.io倉庫去了

原因:

在配置insecure-registry時,docker 必須配置伺服器的 FQDN或者IP位址.不能是伺服器的hostname(比如harbor)

尼恩配置的 是cdh1,推不上去。

FQDN是什麼意思?

FQDN(fully qualified domain name)完全限定域名,是網際網路上特定計算機或主機的完整域名。

FQDN 由兩部分組成:主機名和域名。

例如,假設郵件伺服器的 FQDN 可能是 mail.chenweiliang.com 。

主機名為mail,主機位于域名chenweiliang.com。

DNS(Domain Name System),負責将 FQDN 轉換為 IP位址,是 Internet 上大多數應用程式的尋址方式。

FQDN:(Fully Qualified Domain Name)完全限定域名:同時包含主機名和域名的名稱。 (通過符号“.”)

以下為解決方法:

配置harbor伺服器的 /etc/hosts,将本地ip位址對應為一條FQDN記錄,

比如 192.168.56.121 harbor.daemon.io

停止harbor後,修改harbor配置 harbor.yml 檔案,将 hostname 配置項改為 harbor.daemon.io (一個FQDN),然後重新配置 harbor.

docker-compose down -v

生成配置檔案

./prepare

啟動habror

docker-comp up -d

(可以在另外一台能通路harbor伺服器的機器上配置 hosts 記錄為 FQDN,然後web通路 harbor檢查是否能正常登陸.)

重新配置 docker daemon 中的配置 registry-mirrors 和 insecure-registries 然後重新開機 docker.

比如:

編輯客戶機/etc/docker/daemon.json檔案

{"insecure-registries":["http://harbor.daemon.io:85"]}

重新開機客戶機docker服務

systemctl restart docker #或者(service docker restart)

之後就能正常pull,并且push本地鏡像到 harbor私服中.

附上完整的:

{
  "registry-mirrors": [
    "https://bjtzu1jb.mirror.aliyuncs.com",
    "http://f1361db2.m.daocloud.io",
    "https://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://reg-mirror.qiniu.com",
    "https://dockerhub.azk8s.cn",
    "https://registry.docker-cn.com",
    "https://harbor.daemon.io"
  ],
  "insecure-registries":["http://harbor.daemon.io:85"]
}           

推送鏡像到Harber

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker 推送指令

在項目中标記鏡像:

docker tag SOURCE_IMAGE[:TAG] harbor.daemon.io/demo/REPOSITORY[:TAG]

docker tag  nginx:latest  harbor.daemon.io/demo/nginx:latest           

推送鏡像到目前項目:

docker push harbor.daemon.io/demo/REPOSITORY[:TAG]

docker push harbor.daemon.io/demo/nginx:latest           

錯誤提升:

[root@centos1 harbor]# docker push harbor.daemon.io/demo/nginx:latest
The push refers to repository [harbor.daemon.io/demo/nginx]
Get "https://harbor.daemon.io/v2/": x509: certificate is valid for cdh1, not harbor.daemon.io           

需要生成證書

openssl req -x509 -new -nodes -sha512 -days 3650 \
-subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=harbor.daemon.io" \
-key ca.key \
-out ca.crt

openssl genrsa -out harbor.daemon.io.key 4096

openssl req -sha512 -new \
-subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=harbor.daemon.io" \
-key harbor.daemon.io.key \
-out harbor.daemon.io.csr


生成一個x509 v3擴充檔案
cat > harbor.daemon.io.v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:harbor.daemon.io
EOF

openssl x509 -req -sha512 -days 3650 \
-extfile harbor.daemon.io.v3.ext \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-in harbor.daemon.io.csr \
-out harbor.daemon.io.crt

openssl x509 -inform PEM -in harbor.daemon.io.crt -out harbor.daemon.io.cert           

harbor.yml中certificate填寫為如上cdh1.cert的檔案目錄位址:

/usr/local/harbor/ssl/harbor.daemon.io.cert

harbor.yml中private_key填寫為如上cdh1.key的檔案目錄位址:

/usr/local/harbor/ssl/harbor.daemon.io.key

mkdir /etc/docker/certs.d/harbor.daemon.io

cp /usr/local/harbor/ssl/harbor.daemon.io.cert /etc/docker/certs.d/harbor.daemon.io/ca.crt

cp /usr/local/harbor/ssl/harbor.daemon.io.cert /etc/docker/certs.d/harbor.daemon.io/harbor.daemon.io.cert 

cp /usr/local/harbor/ssl/harbor.daemon.io.cert /etc/docker/certs.d/harbor.daemon.io/ca.crt


docker-compose down

prepare

systemctl restart docker 

docker-compose up -d

docker login  harbor.daemon.io           

推送成功

推送鏡像到目前項目:

docker push harbor.daemon.io/demo/REPOSITORY[:TAG]

docker push harbor.daemon.io/demo/nginx:latest           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

界面展示

http://cdh1:85/

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker Image概述

什麼是Image

  • 檔案和meta data的集合(root filesystem)
  • 分層的,并且每一層都可以添加改變删除檔案,成為一個新的image
  • 不同的image可以共享相同的layer
  • Image本身是read-only的

Image的擷取

  • 方式1:Build from Dockerfile
  • 方式2:Pull from Registry

例如:輸入指令 sudo docker pull ubuntu:14.04,則會從Registry中拉出Image,Registry類似于github的作用,預設都是從docker hub上面拉取,上面會有官方和第三方的版本

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

輸入指令 sudo docker image ls則可以顯示所拉去的Image

如何做一個自己的Base Image

1.首先建立一個可以執行的程式,下面用一個C語言的hello程式做例子

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

2.通過dockerfile把這個可執行檔案打成一個Image

我們在hellow檔案目前目錄下建立一個Dockerfile檔案,如下:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

3.執行指令:docker build -t yunduan/hello-world . (-t表示訓示表情,"."表示在目前路徑下尋找dockerfile),執行以後,出現如下界面表示執行成功。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

4.檢視image,則可以看到成功build了一個image,執行指令docker history +[IMAGE ID]則可以檢視鏡像的層級

執行指令docker run + [鏡像标簽名]則可以生成一個container運作程式。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

建構自己的Docker鏡像

  • 指令一:docker container commit(Create a new image from a container's changes)

這個指令表示當你在容器中做出了改變之後,可以重新建構Image

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

通過這個例子可以看出,其就在centos鏡像上重新建構了一層

  • 指令二:docker image build(Build an image from a Dockerfile)

Dockerfile文法

  • FROM

原則:盡量使用官方的image作為base image!

FROM scratch #制作base image
FROM centos  #使用base image
FROM ubuntu:14.04           
  • LABEL

原則:Metadata不可少!相當于代碼的注釋

LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="This is description"           
  • RUN

作用:執行指令并建立新的Image Layer

原則:為了美觀,複雜的RUN請用反斜線換行!避免無用分層,合并多條指令成一行!

RUN yum update && yum install -y vim \
     python-dev   #反斜線換行
RUN apt-get update && apt-get install -y perl \
    pwgen --no-install-recommends && rm -rf \
    /var/lib/apt/lists/*    #注意清理cache
RUN /bin/bash -c 'source $HOME/.bashrc;echo
$HOME'           
  • WORKDIR

作用:設定目前目錄,類似于cd

原則:用WORKDIR,不要用RUN cd!盡量使用絕對目錄

WORKDIR /root
WORKDIR /test  #如果沒有會自動建立test目錄
WORKDIR demo
RUN pwd        #輸出結果應該是 /test/demo           
  • ADD and COPY

作用:把本地檔案添加到Docker image裡面

原則:大部分情況,COPY優先于ADD!ADD除了COPY還有額外功能(解壓)!添加遠端檔案/目錄請使用curl或者wget!

ADD hello /
ADD test.tar.gz /   #添加到根目錄并解壓
WORKDIR /root
ADD hello test/     # /root/test/hello
WORKDIR /root
COPY hello test/           
  • ENV

作用:設定一個環境變量,引用常量

原則:盡量使用ENV增加可維護性!

ENV MYSQL_VERSION 5.6    # 設定常量
RUN apt-get install -y mysql-server= "${MYSQL_VERSION}" \
    && rm -rf /var/lib/apt/lists/*   # 引用常量           
  • VOLUME and EXPOSE (存儲和網絡)
  • CMD and ENTRYPOINT

CMD:設定容器啟動後預設執行的指令和參數

注釋:1.容器啟動時預設執行的指令 2.如果docker run指定了其他指令,CMD指令被忽略 3.如果定義了多個CMD,隻有最後一個會執行

ENTRYPOINT:設定容器啟動時運作的指令

注釋:1.讓容器以應用程式或者服務的形式運作 2.不會被忽略,一定會執行 3.最佳實踐:寫一個shell腳本作為entrypoint

1.Shell格式

RUN apt-get install -y vim
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"           

2.Exec格式

RUN ["apt-get","install","-y","vim"]
CMD [" /bin/echo" , "hello docker" ]
ENTRYPOINT ["/bin/echo" , "hello docker"]           

3.Shell和Exec格式

  • Dockerfile1 A
FROM centos
ENV name Docker
ENTRYPOINT echo "hello $name"           
  • Dockerfile2
FROM centos
ENV name Dokcer
ENTRYPOINT ["/bin/bash", "-c","echo hello $name" ]           

鏡像釋出

  • docker login
  • docker push

Docker程序與主控端程序的對應關系

Linux通過程序ID檢視檔案路徑

子程序的檔案路徑

[root@VM-4-17-centos ~]#  ls -l /proc/27880
total 0
dr-xr-xr-x 2 root root 0 Nov  3 16:41 attr
-rw-r--r-- 1 root root 0 Nov  3 16:41 autogroup
-r-------- 1 root root 0 Nov  3 16:41 auxv
-r--r--r-- 1 root root 0 Nov  3 16:14 cgroup
--w------- 1 root root 0 Nov  3 16:41 clear_refs
-r--r--r-- 1 root root 0 Nov  3 16:15 cmdline
-rw-r--r-- 1 root root 0 Nov  3 16:41 comm
-rw-r--r-- 1 root root 0 Nov  3 16:41 coredump_filter
-r--r--r-- 1 root root 0 Nov  3 16:41 cpuset
lrwxrwxrwx 1 root root 0 Nov  3 16:41 cwd -> /
-r-------- 1 root root 0 Nov  3 16:41 environ
lrwxrwxrwx 1 root root 0 Nov  3 16:14 exe -> /usr/sbin/sshd
dr-x------ 2 root root 0 Nov  3 16:14 fd
dr-x------ 2 root root 0 Nov  3 16:41 fdinfo
-rw-r--r-- 1 root root 0 Nov  3 16:41 gid_map
-r-------- 1 root root 0 Nov  3 16:41 io
-r--r--r-- 1 root root 0 Nov  3 16:41 limits
-rw-r--r-- 1 root root 0 Nov  3 16:41 loginuid
dr-x------ 2 root root 0 Nov  3 16:41 map_files
-r--r--r-- 1 root root 0 Nov  3 16:41 maps
-rw------- 1 root root 0 Nov  3 16:41 mem
-r--r--r-- 1 root root 0 Nov  3 16:14 mountinfo
-r--r--r-- 1 root root 0 Nov  3 16:41 mounts
-r-------- 1 root root 0 Nov  3 16:41 mountstats
dr-xr-xr-x 5 root root 0 Nov  3 16:41 net
dr-x--x--x 2 root root 0 Nov  3 16:14 ns
-r--r--r-- 1 root root 0 Nov  3 16:41 numa_maps
-rw-r--r-- 1 root root 0 Nov  3 16:41 oom_adj
-r--r--r-- 1 root root 0 Nov  3 16:41 oom_score
-rw-r--r-- 1 root root 0 Nov  3 16:41 oom_score_adj
-r--r--r-- 1 root root 0 Nov  3 16:41 pagemap
-r-------- 1 root root 0 Nov  3 16:41 patch_state
-r--r--r-- 1 root root 0 Nov  3 16:41 personality
-rw-r--r-- 1 root root 0 Nov  3 16:41 projid_map
lrwxrwxrwx 1 root root 0 Nov  3 16:41 root -> /
-rw-r--r-- 1 root root 0 Nov  3 16:41 sched
-r--r--r-- 1 root root 0 Nov  3 16:41 schedstat
-r--r--r-- 1 root root 0 Nov  3 16:41 sessionid
-rw-r--r-- 1 root root 0 Nov  3 16:41 setgroups
-r--r--r-- 1 root root 0 Nov  3 16:41 smaps
-r--r--r-- 1 root root 0 Nov  3 16:41 stack
-r--r--r-- 1 root root 0 Nov  3 16:14 stat
-r--r--r-- 1 root root 0 Nov  3 16:41 statm
-r--r--r-- 1 root root 0 Nov  3 16:14 status
-r--r--r-- 1 root root 0 Nov  3 16:41 syscall
dr-xr-xr-x 3 root root 0 Nov  3 16:41 task
-r--r--r-- 1 root root 0 Nov  3 16:41 timers
-rw-r--r-- 1 root root 0 Nov  3 16:14 uid_map
-r--r--r-- 1 root root 0 Nov  3 16:41 wchan           

以下是/proc目錄中程序27880的資訊說明:

proc/27880 pid為N的程序資訊

/proc/27880/cmdline 程序啟動指令

/proc/27880/cwd 連結到程序目前工作目錄

/proc/27880/environ 程序環境變量清單

/proc/27880/exe 連結到程序的執行指令檔案

/proc/27880/fd 包含程序相關的所有的檔案描述符

/proc/27880/maps 與程序相關的記憶體映射資訊

/proc/27880/mem 指代程序持有的記憶體,不可讀

/proc/27880/root 連結到程序的根目錄

/proc/27880/stat 程序的狀态

/proc/27880/statm 程序使用的記憶體的狀态

/proc/27880/status 程序狀态資訊,比stat/statm更具可讀性           

容器的PID namespace(命名空間)

在Docker中,程序管理的基礎就是Linux核心中的PID名空間技術。

在不同PID名空間中,程序ID是獨立的;即在兩個不同名空間下的程序可以有相同的PID。

在Docker中,每個Container程序預設都具有不同的PID名空間。通過名空間技術,Docker實作容器間的程序隔離。

docker中運作的容器程序,本質上還是運作在主控端上的,是以也會擁有相對應的PID

找出容器ID

docker ps           

輸出

[root@VM-4-17-centos ~]# docker ps
CONTAINER ID        IMAGE                                     COMMAND                  CREATED             STATUS                          PORTS                                              NAMES
460d68823930        lemonbar/centos6-ssh:latest               "/bin/sh -c '/usr/sb…"   32 minutes ago      Up 32 minutes                   0.0.0.0:6021->22/tcp, 0.0.0.0:6081->80/tcp         centos6-2           

檢視容器資訊

docker inspect id           

輸出

[root@VM-4-17-centos ~]# docker inspect  460d68823930
[root@VM-4-17-centos ~]#  docker inspect  460d68823930
[
    {
        "Id": "460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd",
        "Created": "2021-11-03T08:24:36.934129599Z",
        "Path": "/bin/sh",
        "Args": [
            "-c",
            "/usr/sbin/sshd -D"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 4962,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2021-11-03T08:24:37.223255812Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:efd998bd6817af509d348b488e3ce4259f9f05632644a7bf574b785bbc8950b8",
        "ResolvConfPath": "/var/lib/docker/containers/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/hostname",
        "HostsPath": "/var/lib/docker/containers/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/hosts",
        "LogPath": "/var/lib/docker/containers/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd-json.log",
        "Name": "/centos6-2",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {
                "22/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "6021"
                    }
                ],
                "80/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "6081"
                    }
                ]
            },
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "shareable",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DiskQuota": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": 0,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/6835c1b48237aafe27e2efabeda92a3a6623f254f88d54b5e6acce454e560dd6-init/diff:/var/lib/docker/overlay2/7139bf0b716c6e0b6a0c709b7043466f9bbfd7024f8ae584061c00b0bd97348c/diff:/var/lib/docker/overlay2/66a3e278259cdcf50741ce30a115baa3bd6247a60c487e4118e85f2f39328f11/diff:/var/lib/docker/overlay2/20e22c4c28ebadb615eb4c7c290253d3eb91cb49722ee2931b0ee628352a5857/diff:/var/lib/docker/overlay2/a3fa9dbebc83a853083205b8f7921c632cd67f64531f4a25cab419a43172e3ae/diff:/var/lib/docker/overlay2/3af7958c9a4e54d24598058a9fa1e85eb35e3d40f766fa498a674b52724ae73e/diff:/var/lib/docker/overlay2/becb65af4396137ed41fe6d516e834e6e6e9120f4edfac8e2ca8dd67cce23268/diff:/var/lib/docker/overlay2/fef055305158cc96906514c447f0eaea05945138896b0b35ac4146b6a2a3e273/diff:/var/lib/docker/overlay2/79158cdf3ba832493ab0d02d560c784208fe51c74236a5a86f7fb4fb50ab6e44/diff:/var/lib/docker/overlay2/86258a18e1110582b819719593687f11f0404d00a41667b3432c3b974fb1ce42/diff:/var/lib/docker/overlay2/8826b2e0068653fb2c5e8a3dbf839470e2b8eef8cf752b5fe901bea1b210849f/diff:/var/lib/docker/overlay2/145301e2738a8a7581c2bbd5beb9bf7a49b247e46642b8084efbc026a1826116/diff:/var/lib/docker/overlay2/f621f37535e0db1fe44902e22dba7ef0844b9a8b562a9daa39a842a49e9cc9bb/diff:/var/lib/docker/overlay2/7b493e4a97907aaa18b97ad2e9120b5bf87c0e9908ee390a35ea6ff546d8cec6/diff",
                "MergedDir": "/var/lib/docker/overlay2/6835c1b48237aafe27e2efabeda92a3a6623f254f88d54b5e6acce454e560dd6/merged",
                "UpperDir": "/var/lib/docker/overlay2/6835c1b48237aafe27e2efabeda92a3a6623f254f88d54b5e6acce454e560dd6/diff",
                "WorkDir": "/var/lib/docker/overlay2/6835c1b48237aafe27e2efabeda92a3a6623f254f88d54b5e6acce454e560dd6/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [],
        "Config": {
            "Hostname": "460d68823930",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "22/tcp": {},
                "80/tcp": {}
            },
            "Tty": true,
            "OpenStdin": true,
            "StdinOnce": false,
            "Env": [
                "HOME=/",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "/usr/sbin/sshd -D"
            ],
            "Image": "docker.io/lemonbar/centos6-ssh:latest",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "ea66261fb6d8089d5b2d585a2dc32b2003365df7118f5f5e898a152fb5b35773",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "22/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "6021"
                    }
                ],
                "80/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "6081"
                    }
                ]
            },
            "SandboxKey": "/var/run/docker/netns/ea66261fb6d8",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "09ad719a4e9115ee56c5fb0f5b0d39c50bf5acaf0a1afacedc13969c82a2969f",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.6",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:06",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "2586283d16a08210c955d705f05e0f6999b59523a84b0c163e33f535af809ddd",
                    "EndpointID": "09ad719a4e9115ee56c5fb0f5b0d39c50bf5acaf0a1afacedc13969c82a2969f",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.6",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:06",
                    "DriverOpts": null
                }
            }
        }
    }
]           

進入相應目錄

cd /sys/fs/cgroup/memory/docker/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/           

輸出

cd /sys/fs/cgroup/memory/docker/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd/
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# ll
total 0
-rw-r--r-- 1 root root 0 Nov  3 16:24 cgroup.clone_children
--w--w--w- 1 root root 0 Nov  3 16:24 cgroup.event_control
-rw-r--r-- 1 root root 0 Nov  3 16:24 cgroup.procs
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.failcnt
--w------- 1 root root 0 Nov  3 16:24 memory.force_empty
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.numa_stat
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.oom_control
---------- 1 root root 0 Nov  3 16:24 memory.pressure_level
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.stat
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.swappiness
-r--r--r-- 1 root root 0 Nov  3 16:24 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Nov  3 16:24 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Nov  3 16:24 notify_on_release
-rw-r--r-- 1 root root 0 Nov  3 16:24 tasks           
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# cat cgroup.procs
4962
11539
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# cat pids.max
max
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# cat tasks
4962
11539
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# cat cgroup.clone_children
0
[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# pwd
/sys/fs/cgroup/pids/docker/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd           

檢視容器目錄裡的程序号

程序号就存在一個檔案裡面

[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# 
cat cgroup.procs
4962           

與前面利用docker top指令,可以讓我們從主控端作業系統中看到容器的程序資訊。

[root@VM-4-17-centos ~]# docker top centos6-2
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                4962                4948                0                   16:24               pts/0               00:00:00            /usr/sbin/sshd -D           

啟動一個程序

我們下面會在 centos6-2容器中,利用docker exec指令啟動一個"sleep"程序

[root@VM-4-17-centos ]# docker exec -d  centos6-2  sleep 2000
[root@VM-4-17-centos ]# docker exec  centos6-2  ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:24 pts/0    00:00:00 /usr/sbin/sshd -D
root         6     0  0 09:06 ?        00:00:00 sleep 2000
root        10     0  0 09:06 ?        00:00:00 ps -ef           

檢視主控端的程序号

[root@VM-4-17-centos ]#  docker top centos6-2
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                4962                4948                0                   16:24               pts/0               00:00:00            /usr/sbin/sshd -D
root                11539               4948                0                   17:06               ?                   00:00:00            sleep 2000           

我們可以清楚的看到exec指令建立的sleep程序屬 centos6-2 容器的名空間,但是它的父程序是Docker 容器的啟動程序。

檢視容器目錄裡的程序号

程序号就存在一個檔案裡面

[root@VM-4-17-centos 460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd]# cat cgroup.procs
4962
11539           
docker exec -d  centos6-2 pstree -p
 
 docker exec -d  centos6-2 ps -auxf
 
  docker exec -d  centos6-2 ll /proc           

輸出

[root@VM-4-17-centos docker]#  docker exec  centos6-2  ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:24 pts/0    00:00:00 /usr/sbin/sshd -D
root         6     0  0 09:06 ?        00:00:00 sleep 2000
root        40     0  0 09:26 ?        00:00:00 ps -ef
[root@VM-4-17-centos docker]#  docker exec  centos6-2  ps -auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        44  0.0  0.0  13360  1012 ?        Rs   09:26   0:00 ps -auxf
root         6  0.0  0.0   4120   316 ?        Ss   09:06   0:00 sleep 2000
root         1  0.0  0.0  66664  3068 pts/0    Ss+  08:24   0:00 /usr/sbin/sshd -D
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
[root@VM-4-17-centos docker]#  docker exec  centos6-2  pstree -p
sshd(1)           

docker daemon (docker守護程序)

pidof dockerd   #檢視docker守護程序pid
lsof -p 3197 | wc -l #docker守護程序打開的檔案數           

在兩個容器中的"centos "是兩個獨立的程序,但是他們擁有相同的父程序 Docker Daemon。

是以Docker可以父子程序的方式在Docker Daemon和Redis容器之間進行互動。

另一個值得注意的方面是,docker exec指令可以進入指定的容器内部執行指令。由它啟動的程序屬于容器的namespace和相應的cgroup。

但是這些程序的父程序是Docker Daemon而非容器的PID1程序。

我們下面會在Redis容器中,利用docker exec指令啟動一個"sleep"程序

docker@default:~$ docker exec -d redis sleep 2000
docker@default:~$ docker exec redis ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
redis        1     0  0 02:26 ?        00:00:00 redis-server *:6379
root        11     0  0 02:26 ?        00:00:00 sleep 2000
root        21     0  0 02:29 ?        00:00:00 ps -ef
docker@default:~$ docker top redis
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
999                 9955                1264                0                   02:12               ?                   00:00:00            redis-server *:6379
root                9984                1264                0                   02:13               ?                   00:00:00            sleep 2000           

我們可以清楚的看到exec指令建立的sleep程序屬Redis容器的名空間,但是它的父程序是Docker Daemon。

如果我們在主控端作業系統中手動殺掉容器的啟動程序(在上文示例中是redis-server),容器會自動結束,而容器名空間中所有程序也會退出。

docker@default:~$ PID=$(docker inspect --format="{{.State.Pid}}" redis)
docker@default:~$ sudo kill $PID
docker@default:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
356eca186321        redis               "/entrypoint.sh redis"   23 minutes ago      Up 4 minutes               6379/tcp            redis2
f6bc57cc1b46        redis               "/entrypoint.sh redis"   23 minutes ago      Exited (0) 4 seconds ago                       redis           

通過以上示例:

  • 每個容器有獨立的PID名空間,
  • 容器的生命周期和其PID1程序一緻
  • 利用docker exec可以進入到容器的名空間中啟動程序

Docker檔案目錄和容器内部操作

Docker預設的檔案目錄位于Linux server的/var/lib/docker 下面。目錄結構如下

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
|-----containers:用于存儲容器資訊
|-----image:用來存儲鏡像中間件及本身資訊,大小,依賴資訊
|-----network
|-----swarm
|-----tmp:docker臨時目錄
|-----trust:docker信任目錄
|-----volumes:docker卷目錄           

還可以通過docker指令确認檔案位置:

docker info           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

檢視某個容器的檔案目錄:

docker exec 容器name ls           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
docker exec centos6-2   ls /proc           
[root@VM-4-17-centos containers]#  docker exec centos6-2  ls /proc
1
103
acpi
buddyinfo
bus
cgroups
cmdline
consoles
cpuinfo
crypto
devices
diskstats
dma
driver
execdomains
fb
filesystems
fs
interrupts
iomem
ioports
irq
kallsyms
kcore
key-users
keys
kmsg
kpagecount
kpageflags
loadavg
locks
mdstat
meminfo
misc
modules
mounts
mtrr
net
pagetypeinfo
partitions
sched_debug
schedstat
scsi
self
slabinfo
softirqs
stat
swaps
sys
sysrq-trigger
sysvipc
timer_list
timer_stats
tty
uptime
version
vmallocinfo
vmstat
zoneinfo           

Docker Daemon 底層原理

作為Docker容器管理的守護程序,Docker Daemon從最初內建在docker指令中(1.11版本前),

到後來的獨立成單獨二進制程式(1.11版本開始),其功能正在逐漸拆分細化,被配置設定到各個單獨的子產品中去。

演進:Docker守護程序啟動

從Docker服務的啟動腳本,也能看見守護程序的逐漸剝離:

在Docker 1.8之前,Docker守護程序啟動的指令為:

docker -d           

這個階段,守護程序看上去隻是Docker client的一個選項。

Docker 1.8開始,啟動指令變成了:

docker daemon           

這個階段,守護程序看上去是docker指令的一個子產品。

Docker 1.11開始,守護程序啟動指令變成了:

dockerd           

其服務的配置檔案為:

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID           

此時已經和Docker client分離,獨立成一個二進制程式了。

當然,守護程序子產品不停的在重構,其基本功能和定位沒有變化。和一般的CS架構系統一樣,守護程序負責和Docker client互動,并管理Docker鏡像、容器。

OCI(Open Container Initiative)

Open Container Initiative,也就是常說的OCI,是由多家公司共同成立的項目,并由linux基金會進行管理,緻力于container runtime的标準的制定和runc的開發等工作。

官方的介紹是

An open governance structure for the express purpose of creating open industry standards around container formats and runtime. – Open Containers Official Site

所謂container runtime,主要負責的是容器的生命周期的管理。oci的runtime spec标準中對于容器的狀态描述,以及對于容器的建立、删除、檢視等操作進行了定義。

目前主要有兩個标準文檔:容器運作時标準 (runtime spec)和 容器鏡像标準(image spec)。

這兩個協定通過 OCI runtime filesytem bundle 的标準格式連接配接在一起,OCI 鏡像可以通過工具轉換成 bundle,然後 OCI 容器引擎能夠識别這個 bundle 來運作容器。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

image spec

OCI 容器鏡像主要包括幾塊内容:

檔案系統:以 layer 儲存的檔案系統,每個 layer 儲存了和上層之間變化的部分,layer 應該儲存哪些檔案,怎麼表示增加、修改和删除的檔案等

config 檔案:儲存了檔案系統的層級資訊(每個層級的 hash 值,以及曆史資訊),以及容器運作時需要的一些資訊(比如環境變量、工作目錄、指令參數、mount 清單),指定了鏡像在某個特定平台和系統的配置。比較接近我們使用 docker inspect <image_id> 看到的内容

manifest 檔案:鏡像的 config 檔案索引,有哪些 layer,額外的 annotation 資訊,manifest 檔案中儲存了很多和目前平台有關的資訊

index 檔案:可選的檔案,指向不同平台的 manifest 檔案,這個檔案能保證一個鏡像可以跨平台使用,每個平台擁有不同的 manifest 檔案,使用 index 作為索引

runtime spec

OCI 對容器 runtime 的标準主要是指定容器的運作狀态,和 runtime 需要提供的指令。下圖可以是容器狀态轉換圖:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker CLI用戶端工具

/usr/bin/docker           

Docker 的用戶端工具,通過CLI與 dockerd API 交流。 CLI 的例子比如docker build … docker run …

Docker Daemon守護程序 (dockerd)

/usr/bin/dockerd           

當然,守護程序子產品不停的在重構,其基本功能和定位沒有變化。

和一般的CS架構系統一樣,守護程序負責和Docker client互動,并管理Docker鏡像、容器。

Containerd

/usr/bin/docker-containerd           

containerd是容器技術标準化之後的産物,為了能夠相容OCI标準,将容器運作時及其管理功能從Docker Daemon剝離。

理論上,即使不運作dockerd,也能夠直接通過containerd來管理容器。

當然,containerd本身也隻是一個守護程序,容器的實際運作時由後面介紹的runC控制。

最近,Docker剛剛宣布開源containerd。從其項目介紹頁面可以看出,containerd主要職責是鏡像管理(鏡像、元資訊等)、容器執行(調用最終運作時元件執行)。

containerd向上為Docker Daemon提供了gRPC接口,使得Docker Daemon屏蔽下面的結構變化,確定原有接口向下相容。向下通過containerd-shim結合runC,使得引擎可以獨立更新,避免之前Docker Daemon更新會導緻所有容器不可用的問題。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

containerd fully leverages the **OCI runtime specification1, image format specifications and OCI reference implementation (runc).

containerd includes a daemon exposing gRPC API over a local UNIX socket. The API is a low-level one designed for higher layers to wrap and extend. Containerd uses RunC to run containers according to the OCI specification.

docker-shim 容器程序

docker-shim是一個真實運作的容器的真實墊片載體,每啟動一個容器都會起一個新的docker-shim的一個程序,

他直接通過指定的三個參數:容器id,boundle目錄(containerd的對應某個容器生成的目錄,一般位于:/var/run/docker/libcontainerd/containerID),

運作是二進制(預設為runc)來調用runc的api建立一個容器(比如建立容器:最後拼裝的指令如下:runc create 。。。。。)

/usr/bin/docker-containerd-shim           

每啟動一個容器都會起一個新的docker-shim的一個程序. 他直接通過指定的三個參數來建立一個容器:

  1. 容器id
  2. boundle目錄(containerd的對應某個容器生成的目錄,一般位于:/var/run/docker/libcontainerd/containerID)
  3. 運作是二進制(預設為runc)來調用runc的api(比如建立容器時,最後拼裝的指令如下:runc create 。。。)

他的作用是:

  1. 它允許容器運作時(即 runC)在啟動容器之後退出,簡單說就是不必為每個容器一直運作一個容器運作時(runC)
  2. 即使在 containerd 和 dockerd 都挂掉的情況下,容器的标準 IO 和其它的檔案描述符也都是可用的
  3. 向 containerd 報告容器的退出狀态

前兩點尤其重要,有了它們就可以在不中斷容器運作的情況下更新或重新開機 dockerd(這對于生産環境來說意義重大)。

runc (OCI reference implementation)

OCI定義了容器運作時标準,runC是Docker按照開放容器格式标準(OCF, Open Container Format)制定的一種具體實作。

runC是從Docker的libcontainer中遷移而來的,實作了容器啟停、資源隔離等功能。

Docker預設提供了docker-runc實作,事實上,通過containerd的封裝,可以在Docker Daemon啟動的時候指定runc的實作。

/usr/bin/docker-runc            

OCI定義了容器運作時标準OCI Runtime Spec support (aka runC),

runC是Docker按照開放容器格式标準(OCF, Open Container Format)制定的一種具體實作。

runC是從Docker的libcontainer中遷移而來的,實作了容器啟停、資源隔離等功能。

Docker預設提供了docker-runc實作,事實上,通過containerd的封裝,可以在Docker Daemon啟動的時候指定runc的實作。

我們可以通過啟動Docker Daemon時增加–add-runtime參數來選擇其他的runC現。例如:

docker daemon --add-runtime "custom=/usr/local/bin/my-runc-replacement"           

Docker、containerd, containerd-shim和runc之間的關系

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

他們之間的關系如下圖:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

我們可以通過啟動一個Docker容器,來觀察程序之間的關聯。

通過runc來啟動一個container的過程

檢視程序資訊

利用docker top指令,可以讓我們從主控端作業系統中看到容器的程序資訊。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

尼恩提示:這裡比較複雜,具體的視訊介紹,請參見稍後的 穿透雲原生視訊進行介紹。

檢視父程序資訊

[root@VM-4-17-centos containerd]# ps -ef | grep 3401           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

尼恩提示:這裡比較複雜,具體的視訊介紹,請參見稍後的 穿透雲原生視訊進行介紹。

檢視程序樹

pstree -l -a -A 3401 -p           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

雖然pstree指令截斷了指令,但我們還是能夠看出,當Docker daemon啟動之後,dockerd和docker-containerd程序一直存在。

當啟動容器之後,docker-containerd程序(也是這裡介紹的containerd元件)會建立docker-containerd-shim程序,其中的參數e9eaef999da9183b9be0b3239881bc6b9c2070f13057c322dfed3d072820e962就是要啟動容器的id。

最後docker-containerd-shim子程序,已經是實際在容器中運作的程序。

docker-containerd-shim另一個參數,是一個和容器相關的目錄

/var/run/docker/containerd/e9eaef999da9183b9be0b3239881bc6b9c2070f13057c322dfed3d072820e962           

其中包括了容器配置和标準輸入、标準輸出、标準錯誤三個管道檔案。

使用下面的

ll /var/run/docker/containerd/e9eaef999da9183b9be0b3239881bc6b9c2070f13057c322dfed3d072820e962           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

尼恩提示:這裡比較複雜,具體的視訊介紹,請參見稍後的《尼恩Java硬核架構班》 穿透雲原生視訊進行介紹。

CRI 運作時接口

kubernetes在初期版本裡,就對多個容器引擎做了相容,是以可以使用docker、rkt對容器進行管理。

以docker為例,kubelet中會啟動一個docker manager,通過直接調用docker的api進行容器的建立等操作。

在k8s 1.5版本之後,kubernetes推出了自己的運作時接口api–CRI(container runtime interface)。

cri接口的推出,隔離了各個容器引擎之間的差異,而通過統一的接口與各個容器引擎之間進行互動。

與oci不同,cri與kubernetes的概念更加貼合,并緊密綁定。

cri不僅定義了容器的生命周期的管理,還引入了k8s中pod的概念,并定義了管理pod的生命周期。

在kubernetes中,pod是由一組進行了資源限制的,在隔離環境中的容器組成。

而這個隔離環境,稱之為PodSandbox。

在cri開始之初,主要是支援docker和rkt兩種。

其中kubelet是通過cri接口,調用docker-shim,并進一步調用docker api實作的。

如上文所述,docker獨立出來了containerd。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

kubernetes也順應潮流,孵化了cri-containerd項目,用以将containerd接入到cri的标準中。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

為了進一步與oci進行相容,kubernetes還孵化了cri-o,成為了架設在cri和oci之間的一座橋梁。

通過這種方式,可以友善更多符合oci标準的容器運作時,接入kubernetes進行內建使用。

可以預見到,通過cri-o,kubernetes在使用的相容性和廣泛性上将會得到進一步加強。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker的技術底座:

Linux 命名空間、控制組和 UnionFS 三大技術支撐了目前 Docker 的實作,也是 Docker 能夠出現的最重要原因。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

底層技術支援

其主要利用Linux的底層技術

  • Namespaces:做隔離pid,net,ipc,mnt,uts
  • Control groups:做資源限制
  • Union file systems:Container和image的分層
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由
  • namespace,命名空間

命名空間,容器隔離的基礎,保證A容器看不到B容器.

6個命名空間:User,Mnt,Network,UTS,IPC,Pid

  • cgroups,Cgroups 是 Control Group 的縮寫,控制組

cgroups 容器資源統計和隔離

主要用到的cgroups子系統:cpu,blkio,device,freezer,memory

實際上 Docker 是使用了很多 Linux 的隔離功能,讓容器看起來像一個輕量級虛拟機在獨立運作,容器的本質是被限制了的 Namespaces,cgroup,具有邏輯上獨立檔案系統,網絡的一個程序。

  • unionfs 聯合檔案系統

典型:aufs/overlayfs,分層鏡像實作的基礎

UnionFS 聯合檔案系統

什麼是鏡像

那麼問題來了,沒有作業系統,怎麼運作程式?

可以在Docker中建立一個centos的鏡像檔案,這樣就能将centos系統內建到Docker中,運作的應用就都是centos的應用。

Image 是 Docker 部署的基本機關,一個 Image 包含了我們的程式檔案,以及這個程式依賴的資源的環境。Docker Image 對外是以一個檔案的形式展示的(更準确的說是一個 mount 點)。

UnionFS 與AUFS

UnionFS 其實是一種為 Linux 作業系統設計的用于把多個檔案系統『聯合』到同一個挂載點的檔案系統服務。

AUFS 即 Advanced UnionFS 其實就是 UnionFS 的更新版,它能夠提供更優秀的性能和效率。

AUFS 作為先進聯合檔案系統,它能夠将不同檔案夾中的層聯合(Union)到了同一個檔案夾中,這些檔案夾在 AUFS 中稱作分支,整個『聯合』的過程被稱為聯合挂載(Union Mount)。

概念了解起來比較枯燥,最好是有一個真實的例子來幫助我們了解:

首先,我們建立 company 和 home 兩個目錄,并且分别為他們創造兩個檔案

$ tree .
.
|-- company
|   |-- code
|   `-- meeting
`-- home
    |-- eat
    `-- sleep           

然後我們将通過 mount 指令把 company 和 home 兩個目錄「聯合」起來,建立一個 AUFS 的檔案系統,并挂載到目前目錄下的 mnt 目錄下:

$ mkdir mnt
$ ll
total 20
drwxr-xr-x 5 root root 4096 Oct 25 16:10 ./
drwxr-xr-x 5 root root 4096 Oct 25 16:06 ../
drwxr-xr-x 4 root root 4096 Oct 25 16:06 company/
drwxr-xr-x 4 root root 4096 Oct 25 16:05 home/
drwxr-xr-x 2 root root 4096 Oct 25 16:10 mnt/

# mount -t aufs -o dirs=./home:./company none ./mnt
# ll
total 20
drwxr-xr-x 5 root root 4096 Oct 25 16:10 ./
drwxr-xr-x 5 root root 4096 Oct 25 16:06 ../
drwxr-xr-x 4 root root 4096 Oct 25 16:06 company/
drwxr-xr-x 6 root root 4096 Oct 25 16:10 home/
drwxr-xr-x 8 root root 4096 Oct 25 16:10 mnt/
root@rds-k8s-18-svr0:~/xuran/aufs# tree ./mnt/
./mnt/
|-- code
|-- eat
|-- meeting
`-- sleep

4 directories, 0 files           

通過 ./mnt 目錄結構的輸出結果,可以看到原來兩個目錄下的内容都被合并到了一個 mnt 的目錄下。

預設情況下,如果我們不對「聯合」的目錄指定權限,核心将根據從左至右的順序将第一個目錄指定為可讀可寫的,其餘的都為隻讀。那麼,當我們向隻讀的目錄做一些寫入操作的話,會發生什麼呢?

$ echo apple > ./mnt/code
$ cat company/code
$ cat home/code
apple           

通過對上面代碼段的觀察,我們可以看出,當寫入操作發生在 company/code 檔案時, 對應的修改并沒有反映到原始的目錄中。

而是在 home 目錄下又建立了一個名為 code 的檔案,并将 apple 寫入了進去。

看起來很奇怪的現象,其實這正是 Union File System 的厲害之處:

Union File System 聯合了多個不同的目錄,并且把他們挂載到一個統一的目錄上。

在這些「聯合」的子目錄中, 有一部分是可讀可寫的,但是有一部分隻是可讀的。

當你對可讀的目錄内容做出修改的時候,其結果隻會儲存到可寫的目錄下,不會影響隻讀的目錄。

比如,我們可以把我們的服務的源代碼目錄和一個存放代碼修改記錄的目錄「聯合」起來構成一個 AUFS。前者設定隻讀權限,後者設定讀寫權限。

那麼,一切對源代碼目錄下檔案的修改都隻會影響那個存放修改的目錄,不會污染原始的代碼。

在 AUFS 中還有一個特殊的概念需要提及一下:

branch – 就是各個要被union起來的目錄。

Stack 結構 - AUFS 它會根據branch 被 Union 的順序形成一個 Stack 的結構,從下至上,最上面的目錄是可讀寫的,其餘都是可讀的。如果按照我們剛剛執行 aufs 挂載的指令來說,最左側的目錄就對應 Stack 最頂層的 branch。

是以:下面的指令中,最為左側的為 home,而不是 company

mount -t aufs -o dirs=./home:./company none ./mnt           

什麼是 Docker 鏡像分層機制?

首先,讓我們來看下 Docker Image 中的 Layer 的概念:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker Image 是有一個層級結構的,最底層的 Layer 為 BaseImage(一般為一個作業系統的 ISO 鏡像),然後順序執行每一條指令,生成的 Layer 按照入棧的順序逐漸累加,最終形成一個 Image。

直覺的角度來說,是這個圖所示:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

每一次都是一個被聯合的目錄,從目錄的角度來說,大緻如下圖所示:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker Image 如何而來呢?

簡單來說,一個 Image 是通過一個 DockerFile 定義的,然後使用 docker build 指令建構它。

DockerFile 中的每一條指令的執行結果都會成為 Image 中的一個 Layer。

這裡,我們通過 Build 一個鏡像,來觀察 Image 的分層機制:

Dockerfile:

# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]           

建構結果:

root@rds-k8s-18-svr0:~/xuran/exampleimage# docker build -t hello ./
Sending build context to Docker daemon  5.12 kB
Step 1/7 : FROM python:2.7-slim
 ---> 804b0a01ea83
Step 2/7 : WORKDIR /app
 ---> Using cache
 ---> 6d93c5b91703
Step 3/7 : COPY . /app
 ---> Using cache
 ---> feddc82d321b
Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt
 ---> Using cache
 ---> 94695df5e14d
Step 5/7 : EXPOSE 81
 ---> Using cache
 ---> 43c392d51dff
Step 6/7 : ENV NAME World
 ---> Using cache
 ---> 78c9a60237c8
Step 7/7 : CMD python app.py
 ---> Using cache
 ---> a5ccd4e1b15d
Successfully built a5ccd4e1b15d           

通過建構結果可以看出,建構的過程就是執行 Dockerfile 檔案中我們寫入的指令。建構一共進行了7個步驟,每個步驟進行完都會生成一個随機的 ID,來辨別這一 layer 中的内容。 最後一行的 a5ccd4e1b15d 為鏡像的 ID。由于我貼上來的建構過程已經是建構了第二次的結果了,是以可以看出,對于沒有任何修改的内容,Docker 會複用之前的結果。

如果 DockerFile 中的内容沒有變動,那麼相應的鏡像在 build 的時候會複用之前的 layer,以便提升建構效率。并且,即使檔案内容有修改,那也隻會重新 build 修改的 layer,其他未修改的也仍然會複用。

通過了解了 Docker Image 的分層機制,我們多多少少能夠感覺到,Layer 和 Image 的關系與 AUFS 中的聯合目錄和挂載點的關系比較相似。

而 Docker 也正是通過 AUFS 來管理 Images 的。

Namespaces

在Linux系統中,Namespace是在核心級别以一種抽象的形式來封裝系統資源,通過将系統資源放在不同的Namespace中,來實作資源隔離的目的。不同的Namespace程式,可以享有一份獨立的系統資源。

命名空間(namespaces)是 Linux 為我們提供的用于分離程序樹、網絡接口、挂載點以及程序間通信等資源的方法。在日常使用 Linux 或者 macOS 時,我們并沒有運作多個完全分離的伺服器的需要,但是如果我們在伺服器上啟動了多個服務,這些服務其實會互相影響的,每一個服務都能看到其他服務的程序,也可以通路主控端器上的任意檔案,這是很多時候我們都不願意看到的,我們更希望運作在同一台機器上的不同服務能做到完全隔離,就像運作在多台不同的機器上一樣。

Linux 的命名空間機制提供了以下七種不同的命名空間,包括 :

  • CLONE_NEWCGROUP、
  • CLONE_NEWIPC、
  • CLONE_NEWNET、
  • CLONE_NEWNS、
  • CLONE_NEWPID、
  • CLONE_NEWUSER 和
  • CLONE_NEWUTS,

通過這七個選項, 我們能在建立新的程序時, 設定新程序應該在哪些資源上與主控端器進行隔離。具體如下:

Namespace Flag Page Isolates
Cgroup CLONE_NEWCGROUP cgroup_namespaces Cgroup root directory
IPC CLONE_NEWIPC ipc_namespaces System V IPC,POSIX message queues 隔離程序間通信
Network CLONE_NEWNET network_namespaces Network devices,stacks, ports, etc. 隔離網絡資源
Mount CLONE_NEWNS mount_namespaces Mount points 隔離檔案系統挂載點
PID CLONE_NEWPID pid_namespaces Process IDs 隔離程序的ID
Time CLONE_NEWTIME time_namespaces Boot and monotonic clocks
User CLONE_NEWUSER user_namespaces User and group IDs 隔離使用者和使用者組的ID
UTS CLONE_NEWUTS uts_namespaces Hostname and NIS domain name 隔離主機名和域名資訊

這裡提出一個問題,在主控端上啟動兩個容器,在這兩個容器内都各有一個 PID=1的程序,衆所周知,Linux 裡 PID 是唯一的,既然 Docker 不是跑在主控端上的兩個虛拟機,那麼它是如何實作在主控端上運作兩個相同 PID 的程序呢?

這裡就用到了 Linux Namespaces,它其實是 Linux 建立新程序時的一個可選參數,在 Linux 系統中建立程序的系統調用是 clone()方法。

int clone(int (*fn) (void *),void *child stack,
          int flags, void *arg, . . .
         /* pid_ t *ptid, void *newtls, pid_ t *ctid */ ) ;           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

通過調用這個方法,這個程序會獲得一個獨立的程序空間,它的 pid 是1,并且看不到主控端上的其他程序,這也就是在容器内執行 PS 指令的結果。

不僅僅是 PID,當你啟動啟動容器之後,Docker 會為這個容器建立一系列其他 namespaces。

這些 namespaces 提供了不同層面的隔離。容器的運作受到各個層面 namespace 的限制。

Docker Engine 使用了以下 Linux 的隔離技術:

The pid namespace: 管理 PID 命名空間 (PID: Process ID).

The net namespace: 管理網絡命名空間(NET: Networking).

The ipc namespace: 管理程序間通信命名空間(IPC: InterProcess Communication).

The mnt namespace: 管理檔案系統挂載點命名空間 (MNT: Mount).

The uts namespace: Unix 時間系統隔離. (UTS: Unix Timesharing System).

通過這些技術,運作時的容器得以看到一個和主控端上其他容器隔離的環境。

程序隔離

程序是 Linux 以及現在作業系統中非常重要的概念,它表示一個正在執行的程式,也是在現代分時系統中的一個任務單元。

在每一個 *nix 的作業系統上,我們都能夠通過 ps 指令列印出目前作業系統中正在執行的程序,比如在 Ubuntu 上,使用該指令就能得到以下的結果:

|$ ps -ef
UID		PID		PPID	C 	STIME 	TTY          TIME CMD
root     1       0  	0   Apr08 	 ?      00:00:09 /sbin/init
root     2       0  	0   Apr08 	 ?      00:00:00 [kthreadd]
root     3       2  	0   Apr08	 ?      00:00:05 [ksoftirqd/0]
root     5       2  	0   Apr08 	 ?      00:00:00 [kworker/0:0H]
root     7       2  	0   Apr08 	 ?     	00:07:10 [rcu_sched]
root    39       2  	0   Apr08 	 ?      00:00:00 [migration/0]
root    40       2  	0   Apr08 	 ?      00:01:54 [watchdog/0]           

目前機器上有很多的程序正在執行,在上述程序中有兩個非常特殊,一個是 pid 為 1 的 /sbin/init 程序,另一個是 pid 為 2 的 kthreadd 程序,這兩個程序都是被 Linux 中的上帝程序 idle 建立出來的,其中前者負責執行核心的一部分初始化工作和系統配置,也會建立一些類似 getty 的注冊程序,而後者負責管理和排程其他的核心程序。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

如果我們在目前的 Linux 作業系統下運作一個新的 Docker 容器,并通過 exec 進入其内部的 bash 并列印其中的全部程序,我們會得到以下的結果:

UID        PID  PPID  C STIME TTY          TIME CMD
root     29407     1  0 Nov16 ?        00:08:38 /usr/bin/dockerd --raw-logs
root      1554 29407  0 Nov19 ?        00:03:28 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root      5006  1554  0 08:38 ?        00:00:00 docker-containerd-shim b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 /var/run/docker/libcontainerd/b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 docker-runc           

在新的容器内部執行 ps 指令列印出了非常幹淨的程序清單,隻有包含目前 ps -ef 在内的三個程序,在主控端器上的幾十個程序都已經消失不見了。

目前的 Docker 容器成功将容器内的程序與主控端器中的程序隔離,如果我們在主控端器上列印目前的全部程序時,會得到下面三條與 Docker 相關的結果:

在目前的主控端器上,可能就存在由上述的不同程序構成的程序樹:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

這就是在使用 clone(2) 建立新程序時傳入 CLONE_NEWPID 實作的,也就是使用 Linux 的命名空間實作程序的隔離,Docker 容器内部的任意程序都對主控端器的程序一無所知。

containerRouter.postContainersStart
└── daemon.ContainerStart
└── daemon.createSpec
    └── setNamespaces
        └── setNamespace           

Docker 的容器就是使用上述技術實作與主控端器的程序隔離,當我們每次運作 docker run 或者 docker start 時,都會在下面的方法中建立一個用于設定程序間隔離的 Spec:

func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
    s := oci.DefaultSpec()

        // ...
        if err := setNamespaces(daemon, &s, c); err != nil {
            return nil, fmt.Errorf("linux spec namespaces: %v", err)
        }

    return &s, nil
}            

在 setNamespaces 方法中不僅會設定程序相關的命名空間,還會設定與使用者、網絡、IPC 以及 UTS 相關的命名空間:

func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
    // user
    // network
    // ipc
    // uts

    // pid
    if c.HostConfig.PidMode.IsContainer() {
        ns := specs.LinuxNamespace{Type: "pid"}
        pc, err := daemon.getPidContainer(c)
            if err != nil {
                return err
            }
        ns.Path = fmt.Sprintf("/proc/%d/ns/pid", pc.State.GetPID())
            setNamespace(s, ns)
    } else if c.HostConfig.PidMode.IsHost() {
        oci.RemoveNamespace(s, specs.LinuxNamespaceType("pid"))
    } else {
        ns := specs.LinuxNamespace{Type: "pid"}
        setNamespace(s, ns)
    }

    return nil
}            

所有命名空間相關的設定 Spec 最後都會作為 Create 函數的入參在建立新的容器時進行設定:

daemon.containerd.Create(context.Background(), container.ID, spec, createOptions)           

所有與命名空間的相關的設定都是在上述的兩個函數中完成的,Docker 通過命名空間成功完成了與主控端程序和網絡的隔。

網絡隔離

如果 Docker 的容器通過 Linux 的命名空間完成了與主控端程序的網絡隔離,但是卻有沒有辦法通過主控端的網絡與整個網際網路相連,就會産生很多限制。

是以 Docker 雖然可以通過命名空間建立一個隔離的網絡環境,但是 Docker 中的服務仍然需要與外界相連才能發揮作用。

每一個使用 docker run 啟動的容器其實都具有單獨的網絡命名空間,Docker 為我們提供了四種不同的網絡模式,Host、Container、None 和 Bridge 模式。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

在這一部分,我們将介紹 Docker 預設的網絡設定模式:網橋模式。

在這種模式下,除了配置設定隔離的網絡命名空間之外,Docker 還會為所有的容器設定 IP 位址。

當 Docker 伺服器在主機上啟動之後會建立新的虛拟網橋 docker0,随後在該主機上啟動的全部服務在預設情況下都與該網橋相連。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

在預設情況下,每一個容器在建立時都會建立一對虛拟網卡,兩個虛拟網卡組成了資料的通道,其中一個會放在建立的容器中,會加入到名為 docker0 網橋中。

我們可以使用如下的指令來檢視目前網橋的接口:

$ brctl show
bridge name bridge id       STP enabled interfaces
docker0     8000.0242a6654980   no      veth3e84d4f
 veth9953b75           

docker0會為每一個容器配置設定一個新的 IP 位址并将 docker0 的 IP 位址設定為預設的網關。

網橋 docker0 通過 iptables 中的配置與主控端器上的網卡相連,所有符合條件的請求都會通過 iptables 轉發到 docker0 并由網橋分發給對應的機器。

$ iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere           

我們在目前的機器上使用 docker run -d -p 6379:6379 redis 指令啟動了一個新的 Redis 容器,在這之後我們再檢視目前 iptables 的 NAT 配置就會看到在 DOCKER 的鍊中出現了一條新的規則:

DNAT       tcp  --  anywhere             anywhere             tcp dpt:6379 to:192.168.0.4:6379           

上述規則會将從任意源發送到目前機器 6379 端口的 TCP 包轉發到 192.168.0.4:6379 所在的位址上。

這個位址其實也是 Docker 為 Redis 服務配置設定的 IP 位址,如果我們在目前機器上直接 ping 這個 IP 位址就會發現它是可以通路到的:

$ ping 192.168.0.4
PING 192.168.0.4 (192.168.0.4) 56(84) bytes of data.
64 bytes from 192.168.0.4: icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from 192.168.0.4: icmp_seq=2 ttl=64 time=0.043 ms
^C
--- 192.168.0.4 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.043/0.056/0.069/0.013 ms           

從上述的一系列現象,我們就可以推測出 Docker 是如何将容器的内部的端口暴露出來并對資料包進行轉發的了;當有 Docker 的容器需要将服務暴露給主控端器,就會為容器配置設定一個 IP 位址,同時向 iptables 中追加一條新的規則。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

當我們使用 redis-cli 在主控端器的指令行中通路 127.0.0.1:6379 的位址時,經過 iptables 的 NAT PREROUTING 将 ip 位址定向到了 192.168.0.4,重定向過的資料包就可以通過 iptables 中的 FILTER 配置,最終在 NAT POSTROUTING 階段将 ip 位址僞裝成 127.0.0.1,到這裡雖然從外面看起來我們請求的是 127.0.0.1:6379,但是實際上請求的已經是 Docker 容器暴露出的端口了。

$ redis-cli -h 127.0.0.1 -p 6379 ping
PONG           

Docker 通過 Linux 的命名空間實作了網絡的隔離,又通過 iptables 進行資料包轉發,讓 Docker 容器能夠優雅地為主控端器或者其他容器提供服務。

Libnetwork

整個網絡部分的功能都是通過 Docker 拆分出來的 libnetwork 實作的,它提供了一個連接配接不同容器的實作,同時也能夠為應用給出一個能夠提供一緻的程式設計接口和網絡層抽象的容器網絡模型。

The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.

libnetwork 中最重要的概念,容器網絡模型由以下的幾個主要元件組成,分别是 Sandbox、Endpoint 和 Network:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

在容器網絡模型中,每一個容器内部都包含一個 Sandbox,其中存儲着目前容器的網絡棧配置,包括容器的接口、路由表和 DNS 設定,Linux 使用網絡命名空間實作這個 Sandbox,每一個 Sandbox 中都可能會有一個或多個 Endpoint,在 Linux 上就是一個虛拟的網卡 veth,Sandbox 通過 Endpoint 加入到對應的網絡中,這裡的網絡可能就是我們在上面提到的 Linux 網橋或者 VLAN。

挂載點

雖然我們已經通過 Linux 的命名空間解決了程序和網絡隔離的問題,在 Docker 程序中我們已經沒有辦法通路主控端器上的其他程序并且限制了網絡的通路,但是 Docker 容器中的程序仍然能夠通路或者修改主控端器上的其他目錄,這是我們不希望看到的。

在新的程序中建立隔離的挂載點命名空間需要在 clone 函數中傳入 CLONE_NEWNS,這樣子程序就能得到父程序挂載點的拷貝,如果不傳入這個參數子程序對檔案系統的讀寫都會同步回父程序以及整個主機的檔案系統。

如果一個容器需要啟動,那麼它一定需要提供一個根檔案系統(rootfs),容器需要使用這個檔案系統來建立一個新的程序,所有二進制的執行都必須在這個根檔案系統中。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

想要正常啟動一個容器就需要在 rootfs 中挂載以上的幾個特定的目錄,除了上述的幾個目錄需要挂載之外我們還需要建立一些符号連結保證系統 IO 不會出現問題。

為了保證目前的容器程序沒有辦法通路主控端器上其他目錄,我們在這裡還需要通過 libcotainer 提供的 pivor_root 或者 chroot 函數改變程序能夠通路個檔案目錄的根節點。

// pivor_root
put_old = mkdir(...);
pivot_root(rootfs, put_old);
chdir("/");
unmount(put_old, MS_DETACH);
rmdir(put_old);

// chroot
mount(rootfs, "/", NULL, MS_MOVE, NULL);
chroot(".");
chdir("/");           

到這裡我們就将容器需要的目錄挂載到了容器中,同時也禁止目前的容器程序通路主控端器上的其他目錄,保證了不同檔案系統的隔離。

Chroot

在這裡不得不簡單介紹一下 chroot(change root),在 Linux 系統中,系統預設的目錄就都是以 / 也就是根目錄開頭的,chroot 的使用能夠改變目前的系統根目錄結構,通過改變目前系統的根目錄,我們能夠限制使用者的權利,在新的根目錄下并不能夠通路舊系統根目錄的結構個檔案,也就建立了一個與原系統完全隔離的目錄結構。

CGroups實體資源限制分組

我們通過 Linux 的命名空間為新建立的程序隔離了檔案系統、網絡并與主控端器之間的程序互相隔離,但是命名空間并不能夠為我們提供實體資源上的隔離,比如 CPU 或者記憶體,如果在同一台機器上運作了多個對彼此以及主控端器一無所知的『容器』,這些容器卻共同占用了主控端器的實體資源。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

如果其中的某一個容器正在執行 CPU 密集型的任務,那麼就會影響其他容器中任務的性能與執行效率,導緻多個容器互相影響并且搶占資源。如何對多個容器的資源使用進行限制就成了解決程序虛拟資源隔離之後的主要問題,而 Control Groups(簡稱 CGroups)就是能夠隔離主控端器上的實體資源,例如 CPU、記憶體、磁盤 I/O 和網絡帶寬。

每一個 CGroup 都是一組被相同的标準和參數限制的程序,不同的 CGroup 之間是有層級關系的,也就是說它們之間可以從父類繼承一些用于限制資源使用的标準和參數。

Linux 的 CGroup 能夠為一組程序配置設定資源,也就是我們在上面提到的 CPU、記憶體、網絡帶寬等資源,通過對資源的配置設定。

Linux 使用檔案系統來實作 CGroup,我們可以直接使用下面的指令檢視目前的 CGroup 中有哪些子系統:

$ lssubsys -m
cpuset /sys/fs/cgroup/cpuset
cpu /sys/fs/cgroup/cpu
cpuacct /sys/fs/cgroup/cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
blkio /sys/fs/cgroup/blkio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb           

大多數 Linux 的發行版都有着非常相似的子系統,而之是以将上面的 cpuset、cpu 等東西稱作子系統,是因為它們能夠為對應的控制組配置設定資源并限制資源的使用。

如果我們想要建立一個新的 cgroup 隻需要在想要配置設定或者限制資源的子系統下面建立一個新的檔案夾,然後這個檔案夾下就會自動出現很多的内容,如果你在 Linux 上安裝了 Docker,你就會發現所有子系統的目錄下都有一個名為 Docker 的檔案夾:

$ ls cpu
cgroup.clone_children  
...
cpu.stat  
docker  
notify_on_release 
release_agent 
tasks

$ ls cpu/docker/
9c3057f1291b53fd54a3d12023d2644efe6a7db6ddf330436ae73ac92d401cf1 
cgroup.clone_children  
...
cpu.stat  
notify_on_release 
release_agent 
tasks           

9c3057xxx 其實就是我們運作的一個 Docker 容器,啟動這個容器時,Docker 會為這個容器建立一個與容器辨別符相同的 CGroup,在目前的主機上 CGroup 就會有以下的層級關系:

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

每一個 CGroup 下面都有一個 tasks 檔案,其中存儲着屬于目前控制組的所有程序的 pid,作為負責 cpu 的子系統,cpu.cfs_quota_us 檔案中的内容能夠對 CPU 的使用作出限制,如果目前檔案的内容為 50000,那麼目前控制組中的全部程序的 CPU 占用率不能超過 50%。

如果系統管理者想要控制 Docker 某個容器的資源使用率就可以在 docker 這個父控制組下面找到對應的子控制組并且改變它們對應檔案的内容,當然我們也可以直接在程式運作時就使用參數,讓 Docker 程序去改變相應檔案中的内容。

當我們使用 Docker 關閉掉正在運作的容器時,Docker 的子控制組對應的檔案夾也會被 Docker 程序移除,Docker 在使用 CGroup 時其實也隻是做了一些建立檔案夾改變檔案内容的檔案操作,不過 CGroup 的使用也确實解決了我們限制子容器資源占用的問題,系統管理者能夠為多個容器合理的配置設定資源并且不會出現多個容器互相搶占資源的問題。

總之:dockers=LXC+AUFS

docker為LXC+AUFS組合:

  • LXC負責資源管理
  • AUFS負責鏡像管理;

而LXC包括cgroup,namespace,chroot等元件,并通過cgroup資源管理,

那麼,從資源管理的角度來看,Docker,LXC, Cgroup三者的關系是怎樣的呢?

cgroup是在底層落實資源管理,LXC在cgroup上面封裝了一層,随後,docker有在LXC封裝了一層;

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Cgroup其實就是linux提供的一種限制記錄,隔離程序組所使用的實體資源管理機制;

也就是說,Cgroup是LXC為實作虛拟化所使用資源管理手段,我們可以這樣說,底層沒有cgroup支援,也就沒有lxc,更别說docker的存在了,這是我們需要掌握和了解的,三者之間的關系概念

我們在把重心轉移到LXC這個相當于中間件上,上述我們提到LXC是建立在cgroup基礎上的,

我們可以粗略的認為LXC=Cgroup+namespace+Chroot+veth+使用者控制腳本;

LXC利用核心的新特性(cgroup)來提供使用者空間的對象,用來保證資源的隔離和對應用系統資源的限制;

Docker容器的檔案系統最早是建立在Aufs基礎上的,Aufs是一種Union FS,

簡單來說就是支援将不同的目錄挂載到同一個虛拟檔案系統之下,并實作一種 layer的概念,

由于Aufs未能加入到linux核心中,考慮到相容性的問題,便加入了Devicemapper的支援,Docker目前預設是建立在Devicemapper基礎上,

devicemapper使用者控件相關部分主要負責配置具體的政策和控制邏輯,

比如邏輯裝置和哪些實體裝置建立映射,怎麼建立這些映射關系等,而具體過濾和重定向IO請求的工作有核心中相關代碼完成,

是以整個device mapper機制由兩部分組成--核心空間的device mapper驅動,

使用者控件的device mapper庫以及它提供的dmsetup工具;

深入解讀docker網絡

當你開始大規模使用docker時,你會發現需要了解很多關于網絡的知識。docker作為目前最火的輕量級容器技術,有很多令人稱道的功能,如docker的鏡像管理。然而,docker同樣有着很多不完善的地方,網絡方面就是Docker比較薄弱的部分。是以,作為一名運維工程師有必要深入了解docker的網絡知識,以滿足更高的網絡需求。作為一名微服開發工程師,簡單了解docker網絡環節即可。本章節首先介紹了Docker自身的3種local網絡工作方式,然後介紹一些docker自定義網絡模式。

docker安裝後會自動建立3種網絡:

  • bridge
  • host
  • none
docker network ls           

docker網絡理論部分

docker使用Linux橋接網卡,在主控端虛拟一個docker容器網橋(docker0),docker啟動一個容器時會根據docker網橋的網段配置設定給容器一個IP位址,稱為Container-IP,同時docker網橋是每個容器的預設網關。

因為在同一主控端内的容器都接入同一個網橋,這樣容器之間就能夠通過容器的Container-IP直接通信。

docker網橋是主控端虛拟出來的,并不是真實存在的網絡裝置,外部網絡是無法尋址到的,這也意味着外部網絡無法通過直接Container-IP通路到容器。

如果容器希望外部通路能夠通路到,可以通過映射容器端口到宿主主機(端口映射),即docker run建立容器時候通過 -p 或 -P 參數來啟用,通路容器的時候就通過[主控端IP]:[容器端口]通路容器。

# 使用指令檢視docker網絡部分
docker info           

Docker網絡模式

Docker網絡模式 配置 說明
host模式 –net=host 容器和主控端共享Network namespace。容器将不會虛拟出自己的網卡,配置自己的IP 等,而是使用主控端的IP和端口。
container模式 –net=container:NAME_or_ID 容器和另外一個容器共享Network namespace。kubernetes中的pod就是多個容器共享一個Network namespace。建立的容器不會建立自己的網卡,配置自己的IP,而是和一個指定的容器共享IP、端口範圍。
none模式 –net=none 容器有獨立的Network namespace,并沒有對其進行任何網絡設定,如配置設定veth pair 和網橋連接配接,配置IP等。該模式關閉了容器的網絡功能。
bridge模式 –net=bridge (預設為該模式)。此模式會為每一個容器配置設定、設定IP等,并将容器連接配接到一個docker0虛拟網橋,通過docker0網橋以及Iptables nat表配置與主控端通信。
Macvlan network 容器具備Mac位址,使其顯示為網絡上的實體裝置
Overlay (覆寫網絡): 利用VXLAN實作的bridge模式

bridge模式

預設的網絡模式。bridge模式下容器沒有一個公有ip,隻有主控端可以直接通路,外部主機是不可見的,但容器通過主控端的NAT規則後可以通路外網。

Bridge 橋接模式的實作步驟主要如下:

  • Docker Daemon 利用 veth pair 技術,在主控端上建立兩個虛拟網絡接口裝置,假設為veth0 和 veth1。而veth pair 技術的特性可以保證無論哪一個 veth 接收到網絡封包,都會将封包傳輸給另一方。
  • Docker Daemon 将 veth0 附加到 Docker Daemon 建立的 docker0網橋上。保證主控端的網絡封包可以發往 veth0;
  • Docker Daemon 将 veth1 添加到 Docker Container 所屬的 namespace 下,并被改名為eth0。如此一來,保證主控端的網絡封包若發往 veth0,則立即會被 eth0 接收,實作主控端到Docker Container網絡的聯通性;同時,也保證 Docker Container 單獨使用 eth0,實作容器網絡環境的隔離性。

Bridge橋接模式的缺陷:

  1. 最明顯的是,該模式下 Docker Container 不具有一個公有 IP,即和主控端的 eth0 不處于同一個網段。導緻的結果是主控端以外的世界不能直接和容器進行通信。
  2. 雖然 NAT 模式經過中間處理實作了這一點,但是 NAT 模式仍然存在問題與不便,如:容器均需要在主控端上競争端口,容器内部服務的通路者需要使用服務發現獲知服務的外部端口等。
  3. 另外 NAT 模式由于是在三層網絡上的實作手段,故肯定會影響網絡的傳輸效率。

注意:

veth裝置是成雙成對出現的,一端是容器内部命名為eth0,一端是加入到網橋并命名的veth(通常命名為veth),它們組成了一個資料傳輸通道,一端進一端出,veth裝置連接配接了兩個網絡裝置并實作了資料通信

host模式

相當于Vmware中的NAT模式,與主控端在同一個網絡中,但沒有獨立IP位址。

如果啟動容器的時候使用host模式,那麼這個容器将不會獲得一個獨立的Network Namespace,而是和主控端共用一個Network Namespace。容器将不會虛拟出自己的網卡,配置自己的IP等,而是使用主控端的IP和端口。但是,容器的其他方面,如檔案系統、程序清單等還是和主控端隔離的。

使用host模式的容器可以直接使用主控端的IP位址與外界通信,容器内部的服務端口也可以使用主控端的端口,不需要進行NAT,host最大的優勢就是網絡性能比較好,但是docker host上已經使用的端口就不能再用了,網絡的隔離性不好。

host 網絡模式需要在容器建立時指定–network=host

host 模式是 bridge 橋接模式很好的補充。采用 host 模式的 Docker Container,可以直接使用主控端的 IP位址與外界進行通信,若主控端的 eth0 是一個公有 IP,那麼容器也擁有這個公有IP。同時容器内服務的端口也可以使用主控端的端口,無需額外進行 NAT 轉換。

host模式可以讓容器共享主控端網絡棧,這樣的好處是外部主機與容器直接通信,但是容器的網絡缺少隔離性。

Host 網絡模式的缺陷:

最明顯的是 Docker Container 網絡環境隔離性的弱化。即容器不再擁有隔離、獨立的網絡環境。

另外,使用 host 模式的 Docker Container 雖然可以讓容器内部的服務和傳統情況無差别、無改造的使用,但是由于網絡隔離性的弱化,該容器會與主控端共享競争網絡棧的使用;

另外,容器内部将不再擁有所有的端口資源,原因是部分端口資源已經被主控端本身的服務占用,還有部分端口已經用以 bridge 網絡模式容器的端口映射。

Container網絡模式

一種特殊host 網絡模式

Container 網絡模式是 Docker 中一種較為特别的網絡的模式。在容器建立時使用–

network=container:vm1指定。(vm1指定的是運作的容器名)

處于這個模式下的 Docker 容器會共享一個網絡環境,這樣兩個容器之間可以使用localhost高效快速通信。

缺陷:它并沒有改善容器與主控端以外世界通信的情況(和橋接模式一樣,不能連接配接主控端以外的其他裝置)。

這個模式指定新建立的容器和已經存在的一個容器共享一個 Network Namespace,而不是和主控端共享。新建立的容器不會建立自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、端口範圍等。

同樣,兩個容器除了網絡方面,其他的如檔案系統、程序清單等還是隔離的。兩個容器的程序可以通過 lo 網卡裝置通信。

none模式

使用none模式,Docker容器擁有自己的Network Namespace,但是,并不為Docker容器進行任何網絡配置。也就是說,這個Docker容器沒有網卡、IP、路由等資訊。需要我們自己為Docker容器添加網卡、配置IP等。

這種網絡模式下容器隻有lo回環網絡,沒有其他網卡。none模式可以在容器建立時通過--network=none來指定。這種類型的網絡沒有辦法聯網,封閉的網絡能很好的保證容器的安全性。

overlay 網絡模式

Overlay 網絡,也稱為覆寫網絡。主要用于docker叢集部署。

Overlay 網絡的實作方式和方案有多種。Docker自身內建了一種,基于VXLAN隧道技術實作。

Overlay 網絡主要用于實作跨主機容器之間的通信。

應用場景:需要管理成百上千個跨主機的容器叢集的網絡時

macvlan 網絡模式

macvlan網絡模式,最主要的特征就是他們的通信會直接基于mac位址進行轉發。

這時主控端其實充當一個二層交換機。Docker會維護着一個MAC位址表,當主控端網絡收到一個資料包後,直接根據mac位址找到對應的容器,再把資料交給對應的容器。

容器之間可以直接通過IP互通,通過主控端上内建的虛拟網絡裝置(建立macvlan網絡時自動建立),但與主機無法直接利用IP互通。

應用場景:由于每個外來的資料包的目的mac位址就是容器的mac位址,這時每個容器對于外面網絡來說就相當于一個真實的實體網絡裝置。是以當需要讓容器來的網絡看起來是一個真實的實體機時,使用macvlan模式

Macvlan是一個新的嘗試,是真正的網絡虛拟化技術的轉折點。Linux實作非常輕量級,因為與傳統的Linux Bridge隔離相比,它們隻是簡單地與一個Linux以太網接口或子接口相關聯,以實作網絡之間的分離和與實體網絡的連接配接。

Macvlan提供了許多獨特的功能,并有充足的空間進一步創新與各種模式。這些方法的兩個進階優點是繞過Linux網橋的正面性能以及移動部件少的簡單性。删除傳統上駐留在Docker主機NIC和容器接口之間的網橋留下了一個非常簡單的設定,包括容器接口,直接連接配接到Docker主機接口。由于在這些情況下沒有端口映射,是以可以輕松通路外部服務。

Macvlan Bridge模式每個容器都有唯一的MAC位址,用于跟蹤Docker主機的MAC到端口映射。

Macvlan驅動程式網絡連接配接到父Docker主機接口。示例是實體接口,例如eth0,用于802.1q VLAN标記的子接口eth0.10(.10代表VLAN 10)或甚至綁定的主機擴充卡,将兩個以太網接口捆綁為單個邏輯接口。 指定的網關由網絡基礎設施提供的主機外部。 每個Macvlan Bridge模式的Docker網絡彼此隔離,一次隻能有一個網絡連接配接到父節點。

每個主機擴充卡有一個理論限制,每個主機擴充卡可以連接配接一個Docker網絡。 同一子網内的任何容器都可以與沒有網關的同一網絡中的任何其他容器進行通信macvlan bridge。 相同的docker network指令适用于vlan驅動程式。 在Macvlan模式下,在兩個網絡/子網之間沒有外部程序路由的情況下,單獨網絡上的容器無法互相通路。這也适用于同一碼頭網絡内的多個子網。

網絡實操

拉取鏡像

docker pull nginx:1.19.3-alpine           

備份鏡像

docker save nginx:1.19.3-alpine -o nginx.1.19.3.alpine.tar           

導入鏡像

docker load -i nginx.1.19.3.alpine.tar           

bridge網絡

本章節學習docker的bridge網絡,bridge網絡表現形式就是docker0這個網絡接口。

容器預設都是通過docker0這個接口進行通信。

也可以通過docker0去和本機的以太網接口連接配接,這樣容器内部才能通路網際網路。

# 檢視docker0網絡,在預設環境中,一個名為docker0的linux bridge自動被建立好了,其上有一個 
docker0 内部接口,IP位址為172.17.0.1/16
ip a
# 檢視docker 網絡
docker network ls
# 檢視bridge網絡詳情。主要關注Containers節點資訊。
docker network inspect bridge           

docker0詳解

運作鏡像

docker run -itd --name nginx1 nginx:1.19.3-alpine
# 檢視bridge網絡詳情。主要關注Containers節點資訊。發現nginx1容器預設使用bridge網絡
docker network inspect bridge           

容器建立時IP位址的配置設定

# 檢視docker100主機網絡。發現多出一塊網卡veth62aef5e@if8
ip a           

Docker 建立一個容器的時候,會執行如下操作:

  • 建立一對虛拟接口/網卡,也就是veth pair,分别放到本地主機和新容器中;
  • 本地主機一端橋接到預設的 docker0 或指定網橋上,并具有一個唯一的名字,如 vetha596da4;
  • 容器一端放到新容器中,并修改名字作為 eth0,這個網卡/接口隻在容器的名字空間可見;
  • 從網橋可用位址段中(也就是與該bridge對應的network)擷取一個空閑位址配置設定給容器的 eth0,并配置預設路由到橋接網卡 vetha596da4。

完成這些之後,容器就可以使用 eth0 虛拟網卡來連接配接其他容器和其他網絡。

如果不指定--network,建立的容器預設都會挂到 docker0 上,使用本地主機上 docker0 接口的 IP 作為所有容器的預設網關。

第一種方式:
docker exec -it nginx1 sh
ip a
第二種方式:
docker exec -it nginx1 ip a           

安裝brctl

yum install -y bridge-utils           

運作指令

brctl show           
Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

多容器之間通訊

docker run -itd --name nginx1 nginx:1.19.3-alpine
docker run -itd --name nginx2 nginx:1.19.3-alpine
docker network inspect bridge
docker exec -it nginx1 sh
ping 172.17.0.2
docker exec -it nginx2 sh
ping 172.17.0.2
ping www.baidu.com
ping nginx1           

容器IP位址會發生變化

docker stop nginx1 nginx2
先啟動nginx2,在啟動nginx1
docker start nginx2
docker start nginx1
docker network inspect bridge           

link容器

學習docker run指令的link參數

  • --link=[]: 添加連結到另一個容器;

使用link的場景:

在企業開發環境中,我們有一個mysql的服務的容器mysql_1,還有一個web應用程式web_1,肯定web_1這台容器肯定要連接配接mysql_1這個資料庫。

前面網絡命名空間的知識告訴我們,兩個容器需要能通信,需要知道對方的具體的IP位址。

生産環境還比較好,IP位址很少變化,但是在我們内部測試環境,容器部署的IP位址是可能不斷變化的,是以,開發人員不能在代碼中寫死資料庫的IP位址。

這個時候,我們就可以利用容器之間link來解決這個問題。不推薦大家使用該參數

下面,我們來介紹如何通過容器名稱來進行ping,而不是通過IP位址。

docker rm -f nginx2
docker run -itd --name nginx2 --link nginx1 nginx:1.19.3-alpine docker exec -it nginx2 sh
ping 172.17.0.2
ping www.baidu.com
ping nginx1           
  • 上面link指令,是在nginx2容器啟動時link到nginx1容器,是以,在nginx2容器裡面可以ping通

    nginx1容器名,link的作用相當于添加了DNS解析。這裡提醒下,在nginx1容器裡去ping nginx2

    容器是不通的,因為link關系是單向的,不可逆。

  • 實際工作中,docker官網已經不推薦我們使用link參數。
  • docker用其他方式替換掉link參數

建立bridge網絡

docker network create -d bridge lagou-bridge           

上面指令參數-d 是指DRIVER的類型,後面的lagou-bridge是network的自定義名稱,這個和docker0是類似的。下面開始介紹,如何把容器連接配接到lagou-bridge這個網絡。

啟動一個nginx的容器nginx3,并通過參數network connect來連接配接lagou-bridge網絡。在啟動容器

nginx3之前,我們檢視目前還沒有容器連接配接到了lagou-bridge這個網絡上。

brctl show
docker network ls
docker network inspect lagou-bridge
docker run -itd --name nginx3 --network lagou-bridge nginx:1.19.3-alpine brctl show
docker network inspect lagou-bridge           

把一個運作中容器連接配接到lagou-bridge網絡

docker network connect lagou-bridge nginx2
docker network inspect lagou-bridge
docker exec -it nginx2 sh
ping nginx3
docker exec -it nginx3 sh
ping nginx2           

none網絡

環境準備,先stop和rm掉全部之前開啟的容器。并且把前面建立的lagou-bridge網絡也删除。當然,更簡單的辦法是使用快照方式。将docker-100主機恢複到docker初始化安裝時。

docker rm -f $(docker ps -aq)
docker network rm lagou-bridge
docker network ls           

啟動一個ngnix的容器nginx1,并且連接配接到none網絡。然後執行docker network inspect none,看看容器資訊

docker run -itd --name nginx1 --network none nginx:1.19.3-alpine docker network inspect none           

注意,容器使用none模式,是沒有實體位址和IP位址。我們可以進入到nginx1容器裡,執行ip a指令看看。隻有一個lo接口,沒有其他網絡接口,沒有IP。也就是說,使用none模式,這個容器是不能被其他容器通路。這種使用場景很少,隻有項目安全性很高的功能才能使用到。例如:密碼加密算法容器。

docker exec -it nginx1 sh
ip a           

host網絡

前面學習none網絡模式特點就是,容器沒有IP位址,不能和其他容器通信。下面來看看host網絡是什麼特點。我們使用前面指令,啟動一個nginx的nginx2容器,連接配接到host網絡。然後docker network inspect host,看看容器資訊。

docker run -itd --name nginx2 --network host nginx:1.19.3-alpine 
docker network inspect host           

這裡來看,也不顯示IP位址。那麼是不是和none一樣,肯定不是,不然也不會設計none和host網絡進行區分。下面我們進入nginx2容器,執行ip a看看效果。我們在容器裡執行ip a,發現列印内容和在linux本機外執行ip a是一樣的。

docker exec -it nginx2 sh
ip a           

這說明什麼呢?

容器使用了host模式,說明容器和外層linux主機共享一套網絡接口。VMware公司的虛拟機管理軟體,其中網絡設定,也有host這個模式,作用也是一樣,虛拟機裡面使用網絡和你自己外層機器是一模一樣的。這種容器和本機使用共享一套網絡接口,缺點還是很明顯的,例如我們知道web伺服器一般端口是80,共享了一套網絡接口,那麼你這台機器上隻能啟動一個nginx端口為80的伺服器

了。否則,出現端口被占用的情況。

本篇很簡單,就是簡單了解下docker中none和host網絡模式。學習重點還是如何使用bridge網絡。

網絡指令彙總

docker network --help
網絡常用指令彙總
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks ls List networks
prune Remove all unused networks
rm Remove one or more networks           

檢視網絡

檢視網絡 – docker network ls
# 作用:
檢視已經建立的網絡對象
# 指令格式:
docker network ls [OPTIONS]
# 指令參數(OPTIONS):
-f, --filter filter 過濾條件(如 'driver=bridge’)
 --format string 格式化列印結果
--no-trunc 不縮略顯示
-q, --quiet 隻顯示網絡對象的ID
# 注意:
預設情況下,docker安裝完成後,會自動建立bridge、host、none三種網絡驅動 
# 指令示範
docker network ls
docker network ls --no-trunc
docker network ls -f 'driver=host'            

建立網絡

建立網絡 – docker network create
# 作用:
建立新的網絡對象
# 指令格式:
docker network create [OPTIONS] NETWORK
# 指令參數(OPTIONS):
-d, --driver string 指定網絡的驅動(預設 "bridge")
--subnet strings 指定子網網段(如192.168.0.0/16、172.88.0.0/24)
--ip-range strings 執行容器的IP範圍,格式同subnet參數
--gateway strings 子網的IPv4 or IPv6網關,如(192.168.0.1)
# 注意:
host和none模式網絡隻能存在一個
docker自帶的overlay 網絡建立依賴于docker swarm(叢集負載均衡)服務
192.168.0.0/16 等于 192.168.0.0~192.168.255.255192.168.8.0/24
172.88.0.0/24 等于 172.88.0.0~172.88.0.255
# 指令示範
docker network ls
docker network create -d bridge my-bridge
docker network ls           

網絡删除

網絡删除 – docker network rm
# 作用:
删除一個或多個網絡
# 指令格式:
docker network rm NETWORK [NETWORK...]
# 指令參數(OPTIONS):
無           

檢視網絡詳細資訊

檢視網絡詳細資訊 docker network inspect
# 作用:
檢視一個或多個網絡的詳細資訊
# 指令格式:
docker network inspect [OPTIONS] NETWORK [NETWORK...]
或者 docker inspect [OPTIONS] NETWORK [NETWORK...]
# 指令參數(OPTIONS):
-f, --format string 根據format輸出結果           

使用網絡

使用網絡 – docker run –-network
# 作用:
為啟動的容器指定網絡模式
# 指令格式:
docker run/create --network NETWORK
# 指令參數(OPTIONS):
無
# 注意:
預設情況下,docker建立或啟動容器時,會預設使用名為bridge的網絡           

網絡連接配接與斷開

網絡連接配接與斷開 – docker network connect/disconnect
# 作用:
将指定容器與指定網絡進行連接配接或者斷開連接配接
# 指令格式:
docker network connect [OPTIONS] NETWORK CONTAINER
docker network disconnect [OPTIONS] NETWORK CONTAINER
# 指令參數(OPTIONS):
-f, --force 強制斷開連接配接(用于disconnect)           

Docker-Compose 簡介

Docker-Compose 項目是Docker官方的開源項目,負責實作對Docker容器叢集的快速編排。

Docker-Compose 項目由 Python 編寫,調用 Docker 服務提供的API來對容器進行管理。是以,隻要所操作的平台支援 Docker API,就可以在其上利用Compose 來進行編排管理。

Docker-Compose 用來實作Docker容器快速編排

通過 Docker-Compose ,不需要使用shell腳本來啟動容器,而使用 YAML 檔案來配置應用程式需要的所有服務,然後使用一個指令,根據 YAML 的檔案配置建立并啟動所有服務。

Docker-compose模闆檔案簡介

Compose允許使用者通過一個docker-compose.yml模闆檔案(YAML 格式)來定義一組相關聯的應用容器為一個項目(project)。

Compose模闆檔案是一個定義服務、網絡和卷的YAML檔案。

Compose模闆檔案預設路徑是目前目錄下的docker-compose.yml,可以使用.yml或.yaml作為檔案擴充名。

Docker-Compose标準模闆檔案應該包含version、services、networks 三大部分,最關鍵的是services和networks兩個部分。

eg:

version: '3.5'
services:
  nacos1:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos1
    privileged: true
    ports:
     - "8001:8001"
     - "8011:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M 
    env_file: 
     - ./nacos.env 
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_1}
        NACOS_APPLICATION_PORT: 8001
        NACOS_SERVERS: ${NACOS_SERVERS}     
    volumes:
     - ./logs_01/:/home/nacos/logs/
     - ./data_01/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
  nacos2:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos2
    privileged: true
    ports:
     - "8002:8002"
     - "8012:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M    
    env_file: 
     - ./nacos.env     
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_2}
        NACOS_APPLICATION_PORT: 8002
        NACOS_SERVERS: ${NACOS_SERVERS}
    volumes:
     - ./logs_02/:/home/nacos/logs/
     - ./data_02/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
  nacos3:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos3
    privileged: true
    ports:
     - "8003:8003"
     - "8013:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M    
    env_file: 
     - ./nacos.env 
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_3}
        NACOS_APPLICATION_PORT: 8003
        NACOS_SERVERS: ${NACOS_SERVERS}         
    volumes:
     - ./logs_03/:/home/nacos/logs/
     - ./data_03/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
networks:
   ha-network-overlay:
     external: true           

Docker-Compose 的編排處出來的部署架構

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

Docker-compose 快速編排

Docker-Compose 項目是Docker官方的開源項目,負責實作對Docker容器叢集的快速編排。

Docker-Compose 項目由 Python 編寫,調用 Docker 服務提供的API來對容器進行管理。是以,隻要所操作的平台支援 Docker API,就可以在其上利用Compose 來進行編排管理。

首先檢查 版本

[root@k8s-master ~]#  /usr/local/bin/docker-compose -version
docker-compose version 1.25.1, build a82fef07           

如果安裝好了,就ok了,如果沒有安裝,則安裝docker

1.從github上下載下傳docker-compose二進制檔案安裝

下載下傳最新版的docker-compose檔案

curl -L https://github.com/docker/compose/releases/download/1.25.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose           
注明:離線安裝包已經提供。上傳後,複制到/usr/local/bin/即可
cp /root/docker-compose  /usr/local/bin/           

添加可執行權限

chmod +x /usr/local/bin/docker-compose           

測試安裝結果

[root@localhost ~]# docker-compose --version 
docker-compose version 1.25.1, build a82fef07

cp /root/docker-compose  /usr/local/bin/
chmod +x /usr/local/bin/docker-compose
docker-compose --version           

Docker-Compose 的編排結構

Docker-Compose 将所管理的容器分為三層

  • 工程(project):一個工程包含多個服務
  • 服務(service):一個服務當中可包括多個容器執行個體
  • 容器(container)

Docker-Compose 運作目錄下的所有檔案(docker-compose.yml、extends檔案 或 環境變量檔案等)組成一個工程,若無特殊指定 工程名即為目前目錄名。

Docker Compose 的核心就是其配置檔案,采用 YAML 格式,預設為 docker-compose.yml 。

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

一個工程當中可包含多個服務,每個服務中定義了容器運作的鏡像、參數、依賴。

一個服務當中可包括多個容器執行個體,但是:Docker-Compose 并沒有解決負載均衡的問題,是以需要借助其它工具實作服務發現及負載均衡,比如 Consul 技術。

Docker-Compose 的工程配置檔案預設為 docker-compose.yml,可通過環境變量 COMPOSB_FILE 或 -f 參數自定義配置檔案,其定義了多個有依賴關系的服務及每個服務運作的容器。

Compose 允許使用者通過一個單獨的 docker-compose.yml 模闆檔案(YAML格式)來定義一組相關聯的應用容器為一個項目 (project)。

YAML模闆檔案文法

預設的模闆檔案是docker-compose.yml,其中定義的每個服務都必須通過image指令指定鏡像,也可以通過build指令(需要Dockerfile)來自動建構。

其他大部分都跟docker run 中類似。 如果使用build指令,在Dockerfile中設定的選項(例如:CMD,EXPOSE,VOLUME,ENV等)将自動被擷取,無需在docker-compose.yml中再次被設定。

docker-compose.yml 文法說明

1、image

指定為鏡像名稱或鏡像ID。

如果鏡像不存在,Compose将嘗試從網際網路拉取這個鏡像,例如: image: ubuntu image: orchardup/postgresql image: a4bc65fd

指定服務的鏡像名,若本地不存在,則 Compose 會去倉庫拉取這個鏡像:

services:
  web:
    image: nginx           

2、build

指定Dockerfile所在檔案夾的路徑。Compose将會利用他自動建構這個鏡像,然後使用這個鏡像。 build: ./dir

3、command

覆寫容器啟動後預設執行的指令。 command: bundle exec thin -p 3000

4、links

連結到其他服務容器,使用服務名稱(同時作為别名)或服務别名(SERVICE:ALIAS)都可以

links:
 - db
 - db:database
 - redis           

注意:使用别名會自動在伺服器中的/etc/hosts 裡建立,如:172.17.2.186 db,相應的環境變量也會被建立。

5、external_links

連結到docker-compose.yml外部的容器,甚至并非是Compose管理的容器。參數格式和links類似。 external_links:

- redis_1
 - project_db_1:mysql
 - project_db_2:sqlserver           

6、ports

暴露端口資訊。格式

主控端器端口:容器端口(HOST:CONTAINER)

或者僅僅指定容器的端口(主控端器将會随機配置設定端口)都可以。

ports:
 - "3306"
 - "8080:80"
 - "127.0.0.1:8090:8001"           

注意:當使用 HOST:CONTAINER 格式來映射端口時,如果你使用的容器端口小于 60 你可能會得到錯誤得結果,因為 YAML 将會解析 xx:yy 這種數字格式為 60 進制。是以建議采用字元串格式。

7、expose

暴露端口,與posts不同的是expose隻可以暴露端口而不能映射到主機,隻供外部服務連接配接使用;僅可以指定内部端口為參數。

expose:
 - "3000"
 - "8000"           

8、volumes

設定卷挂載的路徑。

可以設定主控端路徑:容器路徑(host:container)或加上通路模式(host:container:ro)ro就是readonly的意思,隻讀模式。

volumes:
 - /var/lib/mysql:/var/lib/mysql
 - /configs/mysql:/etc/configs/:ro           

9、volunes_from

挂載另一個服務或容器的所有資料卷。

volumes_from:
 - service_name
 - container_name           

10、environment

設定環境變量。可以屬于數組或字典兩種格式。

如果隻給定變量的名稱則會自動加載它在Compose主機上的值,可以用來防止洩露不必要的資料。

environment:
 - RACK_ENV=development
 - SESSION_SECRET           

11、env_file

從檔案中擷取環境變量,可以為單獨的檔案路徑或清單。 如果通過docker-compose -f FILE指定了模闆檔案,則env_file中路徑會基于模闆檔案路徑。 如果有變量名稱與environment指令沖突,則以後者為準。

env_file: .env
env_file:
 - ./common.env
 - ./apps/web.env
 - /opt/secrets.env           

環境變量檔案中每一行都必須有注釋,支援#開頭的注釋行。

# common.env: Set Rails/Rack environment
RACK_ENV=development           

12、extends

基于已有的服務進行服務擴充。例如我們已經有了一個webapp服務,模闆檔案為common.yml.

# common.yml
webapp:
build: ./webapp
environment:
 - DEBUG=false
 - SEND_EMAILS=false           

編寫一個新的 development.yml 檔案,使用 common.yml 中的 webapp 服務進行擴充。 development.yml

web:
extends:
file: common.yml
service: 
  webapp:
    ports:
      - "8080:80"
    links:
      - db
    envelopment:
      - DEBUG=true
   db:
    image: mysql:5.7           

後者會自動繼承common.yml中的webapp服務及相關的環境變量。

13、net

設定網絡模式。使用和docker client 的 --net 參數一樣的值。

# 容器預設連接配接的網絡,是所有Docker安裝時都預設安裝的docker0網絡.
net: "bridge"
# 容器定制的網絡棧.
net: "none"
# 使用另一個容器的網絡配置
net: "container:[name or id]"
# 在宿主網絡棧上添加一個容器,容器中的網絡配置會與宿主的一樣
net: "host"           

Docker會為每個節點自動建立三個網絡: 網絡名稱 作用 bridge 容器預設連接配接的網絡,是所有Docker安裝時都預設安裝的docker0網絡 none 容器定制的網絡棧 host 在宿主網絡棧上添加一個容器,容器中的網絡配置會與宿主的一樣 附錄: 操作名稱 指令 建立網絡 docker network create -d bridge mynet 檢視網絡清單 docker network ls

14、pid

和主控端系統共享程序命名空間,打開該選項的容器可以互相通過程序id來通路和操作。

pid: "host"           

15、dns

配置DNS伺服器。可以是一個值,也可以是一個清單。
dns: 8.8.8.8
dns:
 - 8.8.8.8
 - 9.9.9.9           

16、cap_add,cap_drop

添加或放棄容器的Linux能力(Capability)。

cap_add:
 - ALL
cap_drop:
 - NET_ADMIN
 - SYS_ADMIN           

17、dns_search

配置DNS搜尋域。可以是一個值也可以是一個清單。

dns_search: example.com
dns_search:
 - domain1.example.com
 \ - domain2.example.com
working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares           

這些都是和 docker run 支援的選項類似。

cpu_shares: 73
working_dir: /code
entrypoint: /code/entrypoint.sh
user: postgresql
hostname: foo
domainname: foo.com
mem_limit: 1000000000
privileged: true
restart: always
stdin_open: true
tty: true           

18、healthcheck

健康檢查,這個非常有必要,等服務準備好以後再上線,避免更新過程中出現短暫的無法通路。

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost/alive"]
  interval: 5s
  timeout: 3s           

其實大多數情況下健康檢查的規則都會寫在 Dockerfile 中:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s CMD curl -f http://localhost/alive || exit 1           

19、depends_on

依賴的服務,優先啟動,例:

depends_on:
  - redis           

20、deploy

部署相關的配置都在這個節點下,例:

deploy:
  mode: replicated
  replicas: 2
  restart_policy:
    condition: on-failure
    max_attempts: 3
  update_config:
    delay: 5s
    order: start-first # 預設為 stop-first,推薦設定先啟動新服務再終止舊的
  resources:
    limits:
      cpus: "0.50"
      memory: 1g
deploy:
  mode: global # 不推薦全局模式(僅個人意見)。
  placement:
    constraints: [node.role == manager]           

若非特殊服務,以上各節點的配置能夠滿足大部分部署場景了。

docker-compose.yml執行個體

version: '3.5'
services:
  nacos1:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos1
    privileged: true
    ports:
     - "8001:8001"
     - "8011:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M 
    env_file: 
     - ./nacos.env 
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_1}
        NACOS_APPLICATION_PORT: 8001
        NACOS_SERVERS: ${NACOS_SERVERS}     
    volumes:
     - ./logs_01/:/home/nacos/logs/
     - ./data_01/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
  nacos2:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos2
    privileged: true
    ports:
     - "8002:8002"
     - "8012:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M    
    env_file: 
     - ./nacos.env     
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_2}
        NACOS_APPLICATION_PORT: 8002
        NACOS_SERVERS: ${NACOS_SERVERS}
    volumes:
     - ./logs_02/:/home/nacos/logs/
     - ./data_02/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
  nacos3:
    restart: always
    image: nacos/nacos-server:${NACOS_VERSION}
    container_name: nacos3
    privileged: true
    ports:
     - "8003:8003"
     - "8013:9555"
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 1024M    
    env_file: 
     - ./nacos.env 
    environment:
        NACOS_SERVER_IP: ${NACOS_SERVER_IP_3}
        NACOS_APPLICATION_PORT: 8003
        NACOS_SERVERS: ${NACOS_SERVERS}         
    volumes:
     - ./logs_03/:/home/nacos/logs/
     - ./data_03/:/home/nacos/data/
     - ./config/:/home/nacos/config/
    networks:
      - ha-network-overlay
networks:
   ha-network-overlay:
     external: true           

YAML 檔案格式 及 編寫注意事項

使用compose對Docker容器進行編排管理時,需要編寫docker-compose.yml檔案,初次編寫時,容易遇到一些比較低級的問題,導緻執行docker-compose up時先解析yml檔案的錯誤。

比較常見的是yml對縮進的嚴格要求。

yml檔案還行後的縮進,不允許使用tab鍵字元,隻能使用空格,而空格的數量也有要求,經過實際測試,發現每一行增加一個空格用于縮進是正常的。

aml 是一種标記語言,它可以很直覺的展示資料序列化格式,可讀性高。類似于XML資料描述語言,文法比 XMAL簡單的很多。

YAML資料結構通過縮進來表示,連續的項目通過減号來表示,鍵值對用冒号分隔,數組用中括号[]括起來, hash用花括号{}括起來。

.....省略1W字

尼恩說明:由于公衆号最多可以釋出5W字

還有1W字放不下....

更多内容,請參見《Docker學習聖經》PDF,到《技術自由圈》公衆号領取

Docker聖經:大白話說Docker底層原理,6W字實作Docker自由

說在後面

通過此PDF,尼恩希望能幫助大家,早日實作 Docker 技術和底層原理的技術自由。

此文PDF 沒有完,後面還有N多内容需要補充。可以關注 尼恩的《技術自由圈》公衆号。

本書 《 Docker 學習聖經 》PDF的 V1版本,後面會持續疊代和更新。供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水準。

技術自由的實作路徑:

實作你的架構自由:

《吃透8圖1模闆,人人可以做架構》PDF

《10Wqps評論中台,如何架構?B站是這麼做的!!!》PDF

《阿裡二面:千萬級、億級資料,如何性能優化? 教科書級 答案來了》PDF

《峰值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?》

《100億級訂單怎麼排程,來一個大廠的極品方案》PDF

《2個大廠 100億級 超大流量 紅包 架構方案》PDF

… 更多架構文章,正在添加中

實作你的響應式 自由:

《響應式聖經:10W字,實作Spring響應式程式設計自由》PDF

這是老版本 《Flux、Mono、Reactor 實戰(史上最全)》PDF

實作你的spring cloud 自由:

《Spring cloud Alibaba 學習聖經》 PDF

《分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)》PDF

《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關系(史上最全)》PDF

實作你的linux 自由:

《Linux指令大全:2W多字,一次實作Linux自由.pdf》

實作你的網絡 自由:

《TCP協定詳解 (史上最全)》PDF

《網絡三張表:ARP表, MAC表, 路由表,實作你的網絡自由!!》PDFPDF

實作你的分布式鎖 自由:

《Redis分布式鎖(圖解 - 秒懂 - 史上最全)》PDF

《Zookeeper 分布式鎖 - 圖解 - 秒懂》PDF

實作你的王者元件 自由:

《隊列之王: Disruptor 原理、架構、源碼 一文穿透》PDF

《緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)》PDF

《緩存之王:Caffeine 的使用(史上最全)》PDF

《Java Agent 探針、位元組碼增強 ByteBuddy(史上最全)》PDF

實作你的面試題 自由:

4000頁《尼恩Java面試寶典》PDF 40個專題

....

注:以上尼恩 架構筆記、面試題 的PDF檔案,請到《技術自由圈》公衆号領取

還需要啥自由,可以告訴尼恩。 尼恩幫你實作.......