文章目錄
- 第一天 走進Docker的世界
- 認識docker
- 為什么出現docker
- 什么是docker
- docker能做什么
- 版本管理
- 發展史
- 小結
- 安裝
- 配置宿主機網卡轉發
- Yum安裝配置docker
- 核心要素及常用操作詳解
- 鏡像(Image)
- 容器(Container)
- 倉庫(Registry)
- 操作演示
- Dockerfile使用
- 多階構建
- 通過1號行程理解容器的本質
- Django應用容器化實踐
- django專案介紹
- 容器化Django專案
- 運行mysql
- 啟動Django應用
- 實作原理
- Namespace 資源隔離
- CGroup 資源限制
- UnionFS 聯合檔案系統
- Docker網路
- 網路模式
- bridge模式
- 抓包演示
- Host模式
- Conatiner模式
- None模式
- 實用技巧
- 實用技巧
第一天 走進Docker的世界
介紹docker的前世今生,了解docker的實作原理,以Django專案為例,帶大家如何撰寫最佳的Dockerfile構建鏡像,通過本章的學習,大家會知道docker的概念及基本操作,并學會構建自己的業務鏡像,并通過抓包的方式掌握Docker最常用的bridge網路模式的通信,
認識docker
- why
- what
- how
為什么出現docker
需要一種輕量、高效的虛擬化能力
Docker 公司位于舊金山,原名dotCloud,底層利用了Linux容器技術(LXC)(在作業系統中實作資源隔離與限制),為了方便創建和管理這些容器,dotCloud 開發了一套內部工具,之后被命名為“Docker”,Docker就是這樣誕生的,
Hypervisor: 一種運行在基礎物理服務器和作業系統之間的中間軟體層,可允許多個作業系統和應用共享硬體 ,常見的VMware的 Workstation 、ESXi、微軟的Hyper-V或者思杰的XenServer,
Container Runtime:通過Linux內核虛擬化能力管理多個容器,多個容器共享一套作業系統內核,因此摘掉了內核占用的空間及運行所需要的耗時,使得容器極其輕量與快速,
什么是docker
基于作業系統內核,提供輕量級虛擬化功能的CS架構的軟體產品,

基于輕量的特性,解決軟體交付程序中的環境依賴
docker能做什么
-
可以把應用程式代碼及運行依賴環境打包成鏡像,作為交付介質,在各環境部署
-
可以將鏡像(image)啟動成為容器(container),并且提供多容器的生命周期進行管理(啟、停、刪)
-
container容器之間相互隔離,且每個容器可以設定資源限額
-
提供輕量級虛擬化功能,容器就是在宿主機中的一個個的虛擬的空間,彼此相互隔離,完全獨立
版本管理
- Docker 引擎主要有兩個版本:企業版(EE)和社區版(CE)
- 每個季度(1-3,4-6,7-9,10-12),企業版和社區版都會發布一個穩定版本(Stable),社區版本會提供 4 個月的支持,而企業版本會提供 12 個月的支持
- 每個月社區版還會通過 Edge 方式發布月度版
- 從 2017 年第一季度開始,Docker 版本號遵循 YY.MM-xx 格式,類似于 Ubuntu 等專案,例如,2018 年 6 月第一次發布的社區版本為 18.06.0-ce

發展史
13年成立,15年開始,迎來了飛速發展,

Docker 1.8之前,使用LXC,Docker在上層做了封裝, 把LXC復雜的容器創建與使用方式簡化為自己的一套命令體系,
之后,為了實作跨平臺等復雜的場景,Docker抽出了libcontainer專案,把對namespace、cgroup的操作封裝在libcontainer專案里,支持不同的平臺型別,
2015年6月,Docker牽頭成立了 OCI(Open Container Initiative開放容器計劃)組織,這個組織的目的是建立起一個圍繞容器的通用標準 , 容器格式標準是一種不受上層結構系結的協議,即不限于某種特定作業系統、硬體、CPU架構、公有云等 , 允許任何人在遵循該標準的情況下開發應用容器技術,這使得容器技術有了一個更廣闊的發展空間,
OCI成立后,libcontainer 交給OCI組織來維護,但是libcontainer中只包含了與kernel互動的庫,因此基于libcontainer專案,后面又加入了一個CLI工具,并且專案改名為runC (https://github.com/opencontainers/runc ), 目前runC已經成為一個功能強大的runtime工具,
Docker也做了架構調整,將容器運行時相關的程式從docker daemon剝離出來,形成了containerd,containerd向上為Docker Daemon提供了gRPC介面,使得Docker Daemon屏蔽下面的結構變化,確保原有介面向下兼容,向下通過containerd-shim結合runC,使得引擎可以獨立升級,避免之前Docker Daemon升級會導致所有容器不可用的問題,

也就是說
- runC(libcontainer)是符合OCI標準的一個實作,與底層系統互動
- containerd是實作了OCI之上的容器的高級功能,比如鏡像管理、容器執行的呼叫等
- Dockerd目前是最上層與CLI互動的行程,接收cli的請求并與containerd協作
小結
- 為了提供一種更加輕量的虛擬化技術,docker出現了
- 借助于docker容器的輕、快等特性,解決了軟體交付程序中的環境依賴問題,使得docker得以快速發展
- Docker是一種CS架構的軟體產品,可以把代碼及依賴打包成鏡像,作為交付介質,并且把鏡像啟動成為容器,提供容器生命周期的管理
- docker-ce,每季度發布stable版本,18.06,18.09,19.03
- 發展至今,docker已經通過制定OCI標準對最初的專案做了拆分,其中runC和containerd是docker的核心專案,理解docker整個請求的流程,對我們深入理解docker有很大的幫助
安裝
配置宿主機網卡轉發
## 若未配置,需要執行如下
$ cat <<EOF > /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward=1
EOF
$ sysctl -p /etc/sysctl.d/docker.conf
Yum安裝配置docker
## 下載阿里源repo檔案
$ curl -o /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo
$ curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
$ yum clean all && yum makecache
## yum安裝
$ yum install docker-ce-20.10.6 -y
## 查看源中可用版本
$ yum list docker-ce --showduplicates | sort -r
## 安裝舊版本
##yum install -y docker-ce-18.09.9
## 配置源加速
## https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
mkdir -p /etc/docker
vi /etc/docker/daemon.json
{
"registry-mirrors" : [
"https://8xpk5wnt.mirror.aliyuncs.com"
]
}
## 設定開機自啟
systemctl enable docker
systemctl daemon-reload
## 啟動docker
systemctl start docker
## 查看docker資訊
docker info
## docker-client
which docker
## docker daemon
ps aux |grep docker
## containerd
ps aux|grep containerd
systemctl status containerd
核心要素及常用操作詳解

三大核心要素:鏡像(Image)、容器(Container)、倉庫(Registry)
鏡像(Image)
打包了業務代碼及運行環境的包,是靜態的檔案,不能直接對外提供服務,
容器(Container)
鏡像的運行時,可以對外提供服務,
倉庫(Registry)
存放鏡像的地方
- 公有倉庫,Docker Hub,阿里,網易…
- 私有倉庫,企業內部搭建
- Docker Registry,Docker官方提供的鏡像倉庫存盤服務
- Harbor, 是Docker Registry的更高級封裝,它除了提供友好的Web UI界面,角色和用戶權限管理,用戶操作審計等功能
- 鏡像訪問地址形式 registry.devops.com/demo/hello:latest,若沒有前面的url地址,則默認尋找Docker Hub中的鏡像,若沒有tag標簽,則使用latest作為標簽, 比如,docker pull nginx,會被決議成docker.io/library/nginx:latest
- 公有的倉庫中,一般存在這么幾類鏡像
- 作業系統基礎鏡像(centos,ubuntu,suse,alpine)
- 中間件(nginx,redis,mysql,tomcat)
- 語言編譯環境(python,java,golang)
- 業務鏡像(django-demo…)
容器和倉庫不會直接互動,都是以鏡像為載體來操作,
-
查看鏡像串列
$ docker images -
如何獲取鏡像
-
從遠程倉庫拉取
$ docker pull nginx:alpine $ docker images -
使用tag命令
$ docker tag nginx:alpine 192.168.11.100:5000/nginx:alpine $ docker images -
本地構建
$ docker build . -t my-nginx:ubuntu -f Dockerfile
-
-
如何通過鏡像啟動容器
$ docker run --name my-nginx-alpine -d nginx:alpine -
如何知道容器內部運行了什么程式?
# 進入容器內部,分配一個tty終端 $ docker exec -ti my-nginx-alpine /bin/sh # ps aux -
docker怎么知道容器啟動后該執行什么命令?
通過docker build來模擬構建一個nginx的鏡像,
-
創建Dockerfile
# 告訴docker使用哪個基礎鏡像作為模板,后續命令都以這個鏡像為基礎 FROM ubuntu # RUN命令會在上面指定的鏡像里執行命令 RUN apt-get update && apt install -y nginx #告訴docker,啟動容器時執行如下命令 CMD ["/usr/sbin/nginx", "-g","daemon off;"] -
構建本地鏡像
$ docker build . -t my-nginx:ubuntu -f Dockerfile # 或者 ocker build -t nginx:ubuntu . #必須在 Dockerfile同一目錄下
-
-
使用新鏡像啟動容器
$ docker run --name my-nginx-ubuntu -d my-nginx:ubuntu -
進入容器查看行程
$ docker exec -ti my-nginx-ubuntu /bin/sh # ps aux
-
如何訪問容器內服務
# 進入容器內部 $ docker exec -ti my-nginx-alpine /bin/sh # ps aux|grep nginx # curl localhost:80 -
宿主機中如何訪問容器服務
# 刪掉舊服務,重新啟動 $ docker rm -f my-nginx-alpine $ docker run --name my-nginx-alpine -d -p 8080:80 nginx:alpine $ curl 192.168.11.100:8080 -
docker client如何與daemon通信
# /var/run/docker.sock $ docker run --name portainer -d -p 9001:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
操作演示

- 查看所有鏡像:
$ docker images
- 拉取鏡像:
$ docker pull nginx:alpine
- 如何唯一確定鏡像:
- image_id
- repository:tag
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine 377c0837328f 2 weeks ago 19.7MB
-
匯出鏡像到檔案中
$ docker save -o nginx-alpine.tar nginx:alpine -
從檔案中加載鏡像
$ docker load -i nginx-alpine.tar -
部署鏡像倉庫
https://docs.docker.com/registry/
## 使用docker鏡像啟動鏡像倉庫服務 $ docker run -d -p 5000:5000 --restart always --name registry registry:2 ## 默認倉庫不帶認證,若需要認證,參考https://docs.docker.com/registry/deploying/#restricting-access -
推送本地鏡像到鏡像倉庫中
$ docker tag nginx:alpine localhost:5000/nginx:alpine $ docker push localhost:5000/nginx:alpine ## 查看倉庫內元資料 $ curl -X GET http://192.168.11.100:5000/v2/_catalog $ curl -X GET http://192.168.11.100:5000/v2/nginx/tags/list ## 鏡像倉庫給外部訪問,不能通過localhost,嘗試使用內網地址192.168.11.100:5000/nginx:alpine $ docker tag nginx:alpine 192.168.11.100:5000/nginx:alpine $ docker push 192.168.11.100:5000/nginx:alpine The push refers to repository [192.168.11.100:5000/nginx] Get https://192.168.11.100:5000/v2/: http: server gave HTTP response to HTTPS client ## docker默認不允許向http的倉庫地址推送,如何做成https的,參考:https://docs.docker.com/registry/deploying/#run-an-externally-accessible-registry ## 我們沒有可信證書機構頒發的證書和域名,自簽名證書需要在每個節點中拷貝證書檔案,比較麻煩,因此我們通過配置daemon的方式,來跳過證書的驗證: $ cat /etc/docker/daemon.json { "registry-mirrors": [ "https://8xpk5wnt.mirror.aliyuncs.com" ], "insecure-registries": [ "192.168.11.100:5000" ] } $ systemctl restart docker $ docker push 192.168.11.100:5000/nginx:alpine $ docker images # IMAGE ID相同,等于起別名或者加快捷方式 REPOSITORY TAG IMAGE ID CREATED SIZE 192.168.11.100:5000/nginx alpine 377c0837328f 4 weeks ago nginx alpine 377c0837328f 4 weeks ago localhost:5000/nginx alpine 377c0837328f 4 weeks ago registry 2 708bc6af7e5e 2 months ago -
洗掉鏡像
docker rmi nginx:alpine -
查看容器串列
## 查看運行狀態的容器串列 $ docker ps ## 查看全部狀態的容器串列 $ docker ps -a -
啟動容器
## 后臺啟動 $ docker run --name nginx -d nginx:alpine ## 映射埠,把容器的埠映射到宿主機中,-p <host_port>:<container_port> $ docker run --name nginx -d -p 8080:80 nginx:alpine ## 資源限制,最大可用記憶體500M $ docker run --memory=500m nginx:alpine -
容器資料持久化
## 掛載主機目錄 $ docker run --name nginx -d -v /opt:/opt nginx:alpine $ docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d -v /opt/mysql/:/var/lib/mysql mysql:5.7 -
進入容器或者執行容器內的命令
$ docker exec -ti <container_id_or_name> /bin/sh $ docker exec <container_id_or_name> hostname -
主機與容器之間拷貝資料
## 主機拷貝到容器 $ echo '123'>/tmp/test.txt $ docker cp /tmp/test.txt nginx:/tmp $ docker exec -ti nginx cat /tmp/test.txt 123 ## 容器拷貝到主機 $ docker cp nginx:/tmp/test.txt ./ -
掛載已有的資料,重新創建鏡像倉庫容器
## 解壓離線鏡像檔案 $ tar zxf registry.tar.gz -C /opt ## 洗掉當前鏡像倉庫容器 $ docker rm -f registry ## 使用docker鏡像啟動鏡像倉庫服務 $ docker run -d -p 5000:5000 --restart always -v /opt/registry:/var/lib/registry --name registry registry:2假設啟動鏡像倉庫服務的主機地址為192.168.11.100,該目錄中已存在的鏡像串列:
現鏡像倉庫地址 原鏡像倉庫地址 192.168.11.100:5000/coreos/flannel:v0.11.0-amd64 quay.io/coreos/flannel:v0.11.0-amd64 192.168.11.100:5000/mysql:5.7 mysql:5.7 192.168.11.100:5000/nginx:alpine nginx:alpine 192.168.11.100:5000/centos:centos7.5.1804 centos:centos7.5.1804 192.168.11.100:5000/elasticsearch/elasticsearch:7.4.2 docker.elastic.co/elasticsearch/elasticsearch:7.4.2 192.168.11.100:5000/fluentd-es-root:v1.6.2-1.0 quay.io/fluentd_elasticsearch/fluentd:v2.5.2 192.168.11.100:5000/kibana/kibana:7.4.2 docker.elastic.co/kibana/kibana:7.4.2 192.168.11.100:5000/kubernetesui/dashboard:v2.0.0-beta5 kubernetesui/dashboard:v2.0.0-beta5 192.168.11.100:5000/kubernetesui/metrics-scraper:v1.0.1 kubernetesui/metrics-scraper:v1.0.1 192.168.11.100:5000/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 192.168.11.100:5000/jenkinsci/blueocean:latest jenkinsci/blueocean:latest 192.168.11.100:5000/sonarqube:7.9-community sonarqube:7.9-community 192.168.11.100:5000/postgres:11.4 postgres:11. -
查看容器日志
## 查看全部日志 $ docker logs nginx ## 實時查看最新日志 $ docker logs -f nginx ## 從最新的100條開始查看 $ docker logs --tail=100 -f nginx -
停止或者洗掉容器
## 停止運行中的容器 $ docker stop nginx ## 啟動退出容器 $ docker start nginx ## 洗掉非運行中狀態的容器 $ docker rm nginx ## 洗掉運行中的容器 $ docker rm -f nginx -
查看容器或者鏡像的明細
## 查看容器詳細資訊,包括容器IP地址等 $ docker inspect nginx ## 查看鏡像的明細資訊 $ docker inspect nginx:alpine
Dockerfile使用
$ docker build . -t ImageName:ImageTag -f Dockerfile
Dockerfile是一堆指令,在docker build的時候,按照該指令進行操作,最終生成我們期望的鏡像
-
FROM 指定基礎鏡像,必須為第一個命令
格式: FROM <image> FROM <image>:<tag> 示例: FROM mysql:5.7 注意: tag是可選的,如果不使用tag時,會使用latest版本的基礎鏡像 -
MAINTAINER 鏡像維護者的資訊
格式: MAINTAINER <name> 示例: MAINTAINER Yongxin Li MAINTAINER inspur_lyx@hotmail.com MAINTAINER Yongxin Li <inspur_lyx@hotmail.com> -
COPY|ADD 添加本地檔案到鏡像中
格式: COPY <src>... <dest> 示例: ADD hom* /mydir/ # 添加所有以"hom"開頭的檔案 ADD test relativeDir/ # 添加 "test" 到 `WORKDIR`/relativeDir/ ADD test /absoluteDir/ # 添加 "test" 到 /absoluteDir/ -
WORKDIR 作業目錄
格式: WORKDIR /path/to/workdir 示例: WORKDIR /a (這時作業目錄為/a) 注意: 通過WORKDIR設定作業目錄后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都會在該目錄下執行 -
RUN 構建鏡像程序中執行命令
格式: RUN <command> 示例: RUN yum install nginx RUN pip install django RUN mkdir test && rm -rf /var/lib/unusedfiles 注意: RUN指令創建的中間鏡像會被快取,并會在下次構建中使用,如果不想使用這些快取鏡像,可以在構建時指定--no-cache引數,如:docker build --no-cache -
CMD 構建容器后呼叫,也就是在容器啟動時才進行呼叫
格式: CMD ["executable","param1","param2"] (執行可執行檔案,優先) CMD ["param1","param2"] (設定了ENTRYPOINT,則直接呼叫ENTRYPOINT添加引數) CMD command param1 param2 (執行shell內部命令) 示例: CMD ["/usr/bin/wc","--help"] CMD ping www.baidu.com 注意: CMD不同于RUN,CMD用于指定在容器啟動時所要執行的命令,而RUN用于指定鏡像構建時所要執行的命令, -
ENTRYPOINT 設定容器初始化命令,使其可執行化
格式: ENTRYPOINT ["executable", "param1", "param2"] (可執行檔案, 優先) ENTRYPOINT command param1 param2 (shell內部命令) 示例: ENTRYPOINT ["/usr/bin/wc","--help"] 注意: ENTRYPOINT與CMD非常類似,不同的是通過docker run執行的命令不會覆寫ENTRYPOINT,而docker run命令中指定的任何引數,都會被當做引數再次傳遞給ENTRYPOINT,Dockerfile中只允許有一個ENTRYPOINT命令,多指定時會覆寫前面的設定,而只執行最后的ENTRYPOINT指令 -
ENV
格式: ENV <key> <value> ENV <key>=<value> 示例: ENV myName John ENV myCat=fluffy -
EXPOSE
格式: EXPOSE <port> [<port>...] 示例: EXPOSE 80 443 EXPOSE 8080 EXPOSE 11211/tcp 11211/udp 注意: EXPOSE并不會讓容器的埠訪問到主機,要使其可訪問,需要在docker run運行容器時通過-p來發布這些埠,或通過-P引數來發布EXPOSE匯出的所有埠
-
基礎環境鏡像
FROM java:8-alpine RUN apk add --update ca-certificates && rm -rf /var/cache/apk/* && \ find /usr/share/ca-certificates/mozilla/ -name "*.crt" -exec keytool -import -trustcacerts \ -keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts -storepass changeit -noprompt \ -file {} -alias {} \; && \ keytool -list -keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts --storepass changeit ENV MAVEN_VERSION 3.5.4 ENV MAVEN_HOME /usr/lib/mvn ENV PATH $MAVEN_HOME/bin:$PATH RUN wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \ tar -zxvf apache-maven-$MAVEN_VERSION-bin.tar.gz && \ rm apache-maven-$MAVEN_VERSION-bin.tar.gz && \ mv apache-maven-$MAVEN_VERSION /usr/lib/mvn RUN mkdir -p /usr/src/app WORKDIR /usr/src/app -
前端鏡像
FROM nginx:1.19.0-alpine LABEL maintainer="mritd <mritd@linux.com>" ARG TZ='Asia/Shanghai' ENV TZ ${TZ} RUN apk upgrade --update \ && apk add bash tzdata curl wget ca-certificates \ && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \ && echo ${TZ} > /etc/timezone \ && rm -rf /usr/share/nginx/html /var/cache/apk/* COPY landscape-animation-experiment /usr/share/nginx/html EXPOSE 80 443 CMD ["nginx", "-g", "daemon off;"] -
java鏡像
FROM java:8u111 ENV JAVA_OPTS "\ -Xmx4096m \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=256m" ENV JAVA_HOME /usr/java/jdk ENV PATH ${PATH}:${JAVA_HOME}/bin COPY target/myapp.jar myapp.jar RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone EXPOSE 9000 CMD java ${JAVA_OPTS} -jar myapp.jar -
golang鏡像
多階段構建
多階構建
https://gitee.com/agagin/href-counter.git
原始構建:
FROM golang:1.13
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go .
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
$ docker build . -t href-counter:v1 -f Dockerfile
多階構建:
FROM golang:1.13 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go .
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:3.10
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
$ docker build . -t href-counter:v2 -f Dockerfile.multi
原則:
- 不必要的內容不要放在鏡像中
- 減少不必要的層檔案
- 減少網路傳輸操作
- 可以適當的包含一些除錯命令
通過1號行程理解容器的本質
$ docker exec -ti my-nginx-alpine /bin/sh
#/ ps aux
容器啟動的時候可以通過命令去覆寫默認的CMD
$ docker run -d --name xxx nginx:alpine <自定義命令>
# <自定義命令>會覆寫鏡像中指定的CMD指令,作為容器的1號行程啟動,
$ docker run -d --name test-3 nginx:alpine echo 123
$ docker run -d --name test-4 nginx:alpine ping www.luffycity.com
本質上講容器是利用namespace和cgroup等技術在宿主機中創建的獨立的虛擬空間,這個空間內的網路、行程、掛載等資源都是隔離的,
$ docker exec -ti my-nginx /bin/sh
#/ ip addr
#/ ls -l /
#/ apt install xxx
#/ #安裝的軟體對宿主機和其他容器沒有任何影響,和虛擬機不同的是,容器間共享一個內核,所以容器內沒法升級內核
Django應用容器化實踐
django專案介紹
-
專案地址:https://gitee.com/agagin/python-demo.git
-
python3 + django + uwsgi + nginx + mysql
-
內部服務埠8002
容器化Django專案
dockerfiles/myblog/Dockerfile
[root@docker01 ~/dockerfile]# vim Dockerfile
# This my first django Dockerfile
# Version 1.0
# Base images 基礎鏡像
FROM centos:centos7.5.1804
#MAINTAINER 維護者資訊
LABEL maintainer="inspur_lyx@hotmail.com"
#ENV 設定環境變數
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
#RUN 執行以下命令
RUN curl -so /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo && rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
RUN yum install -y python36 python3-devel gcc pcre-devel zlib-devel make net-tools nginx
#作業目錄
WORKDIR /opt/myblog
#拷貝檔案至作業目錄
COPY . .
# 拷貝nginx組態檔
COPY myblog.conf /etc/nginx
#安裝依賴的插件
RUN pip3 install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
RUN chmod +x run.sh && rm -rf ~/.cache/pip
#EXPOSE 映射埠
EXPOSE 8002
#容器啟動時執行命令
CMD ["./run.sh"]
執行構建:
$ docker build . -t myblog:v1 -f Dockerfile
運行mysql
$ docker run -d -p 3306:3306 --name mysql -v /opt/mysql:/var/lib/mysql -e MYSQL_DATABASE=myblog -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
## 引數傳遞
## 查看資料庫
$ docker exec -ti mysql bash
#/ mysql -uroot -p123456
#/ show databases;
## navicator連接
啟動Django應用
## 啟動容器
$ docker run -d -p 8002:8002 --name myblog -e MYSQL_HOST=192.168.11.100 -e MYSQL_USER=root -e MYSQL_PASSWD=123456 myblog:v1
## migrate
$ docker exec -ti myblog bash
#/ python3 manage.py makemigrations
#/ python3 manage.py migrate
#/ python3 manage.py createsuperuser
## 創建超級用戶
$ docker exec -ti myblog python3 manage.py createsuperuser
## 收集靜態檔案
## $ docker exec -ti myblog python3 manage.py collectstatic
訪問192.168.11.100:8002/admin
實作原理
docker優勢:
-
輕量級的虛擬化
-
容器快速啟停
虛擬化核心需要解決的問題:資源隔離與資源限制
- 虛擬機硬體虛擬化技術, 通過一個 hypervisor 層實作對資源的徹底隔離,
- 容器則是作業系統級別的虛擬化,利用的是內核的 Cgroup 和 Namespace 特性,此功能完全通過軟體實作,
Namespace 資源隔離
命名空間是全域資源的一種抽象,將資源放到不同的命名空間中,各個命名空間中的資源是相互隔離的,
| 分類 | 系統呼叫引數 | 相關內核版本 |
|---|---|---|
| Mount namespaces | CLONE_NEWNS | Linux 2.4.19 |
| UTS namespaces | CLONE_NEWUTS | Linux 2.6.19 |
| IPC namespaces | CLONE_NEWIPC | Linux 2.6.19 |
| PID namespaces | CLONE_NEWPID | Linux 2.6.24 |
| Network namespaces | CLONE_NEWNET | 始于Linux 2.6.24 完成于 Linux 2.6.29 |
| User namespaces | CLONE_NEWUSER | 始于 Linux 2.6.23 完成于 Linux 3.8 |
我們知道,docker容器對于作業系統來講其實是一個行程,我們可以通過原始的方式來模擬一下容器實作資源隔離的基本原理:
linux系統中,通常可以通過clone()實作行程創建的系統呼叫 ,原型如下:
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
- child_func : 傳入子行程運行的程式主函式,
- child_stack : 傳入子行程使用的堆疊空間,
- flags : 表示使用哪些
CLONE_*標志位, - args : 用于傳入用戶引數,
示例一:實作行程獨立的UTS空間
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container - inside the container!\n");
sethostname("container",10); /* 設定hostname */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent - start a container!\n");
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS | SIGCHLD , NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
執行編譯并測驗:
$ gcc -o ns_uts ns_uts.c
$ ./ns_uts
$ hostname
示例二:實作容器獨立的行程空間
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container",10); /* 設定hostname */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent [%5d] - start a container!\n", getpid());
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD , NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
執行編譯并測驗:
$ gcc -o ns_pid ns_pid.c
$ ./ns_pid
$ echo $$
如何確定行程是否屬于同一個namespace:
$ ./ns_pid
Parent [ 8061] - start a container!
$ pstree -p 8061
pid1(8061)───bash(8062)───pstree(8816)
$ ls -l /proc/8061/ns
lrwxrwxrwx 1 root root 0 Jun 24 12:51 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 net -> net:[4026531968]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 uts -> uts:[4026531838]
$ ls -l /proc/8062/ns
lrwxrwxrwx 1 root root 0 Jun 24 12:51 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 net -> net:[4026531968]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 pid -> pid:[4026534845]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 24 12:51 uts -> uts:[4026534844]
## 發現pid和uts是和父行程使用了不同的ns,其他的則是繼承了父行程的命名空間
綜上:通俗來講,docker在啟動一個容器的時候,會呼叫Linux Kernel Namespace的介面,來創建一塊虛擬空間,創建的時候,可以支持設定下面這幾種(可以隨意選擇),docker默認都設定,
- pid:用于行程隔離(PID:行程ID)
- net:管理網路介面(NET:網路)
- ipc:管理對 IPC 資源的訪問(IPC:行程間通信(信號量、訊息佇列和共享記憶體))
- mnt:管理檔案系統掛載點(MNT:掛載)
- uts:隔離主機名和域名
- user:隔離用戶和用戶組
CGroup 資源限制
通過namespace可以保證容器之間的隔離,但是無法控制每個容器可以占用多少資源, 如果其中的某一個容器正在執行 CPU 密集型的任務,那么就會影響其他容器中任務的性能與執行效率,導致多個容器相互影響并且搶占資源,如何對多個容器的資源使用進行限制就成了解決行程虛擬資源隔離之后的主要問題,

Control Groups(簡稱 CGroups)
cgroups是Linux內核提供的一種機制,這種機制可以根據需求吧一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組中,從而為系統資源管理提供一個統一的框架,
CGroups能夠隔離宿主機器上的物理資源,例如 CPU、記憶體、磁盤 I/O ,每一個 CGroup 都是一組被相同的標準和引數限制的行程,而我們需要做的,其實就是把容器這個行程加入到指定的Cgroup中,深入理解CGroup.
UnionFS 聯合檔案系統
Linux namespace和cgroup分別解決了容器的資源隔離與資源限制,那么容器是很輕量的,通常每臺機器中可以運行幾十上百個容器, 這些個容器是共用一個image,還是各自將這個image復制了一份,然后各自獨立運行呢? 如果每個容器之間都是全量的檔案系統拷貝,那么會導致至少如下問題:
- 運行容器的速度會變慢
- 容器和鏡像對宿主機的磁盤空間的壓力
怎么解決這個問題------Docker的存盤驅動
- 鏡像分層存盤
- UnionFS
Docker 鏡像是由一系列的層組成的,每層代表 Dockerfile 中的一條指令,比如下面的 Dockerfile 檔案:
FROM ubuntu:15.04
COPY . /app
RUN make /app
CMD python /app/app.py
這里的 Dockerfile 包含4條命令,其中每一行就創建了一層,下面顯示了上述Dockerfile構建出來的鏡像運行的容器層的結構:

鏡像就是由這些層一層一層堆疊起來的,鏡像中的這些層都是只讀的,當我們運行容器的時候,就可以在這些基礎層至上添加新的可寫層,也就是我們通常說的容器層,對于運行中的容器所做的所有更改(比如寫入新檔案、修改現有檔案、洗掉檔案)都將寫入這個容器層,
對容器層的操作,主要利用了寫時復制(CoW)技術,CoW就是copy-on-write,表示只在需要寫時才去復制,這個是針對已有檔案的修改場景, CoW技術可以讓所有的容器共享image的檔案系統,所有資料都從image中讀取,只有當要對檔案進行寫操作時,才從image里把要寫的檔案復制到自己的檔案系統進行修改,所以無論有多少個容器共享同一個image,所做的寫操作都是對從image中復制到自己的檔案系統中的復本上進行,并不會修改image的源檔案,且多個容器操作同一個檔案,會在每個容器的檔案系統里生成一個復本,每個容器修改的都是自己的復本,相互隔離,相互不影響,使用CoW可以有效的提高磁盤的利用率,

鏡像中每一層的檔案都是分散在不同的目錄中的,如何把這些不同目錄的檔案整合到一起呢?
UnionFS 其實是一種為 Linux 作業系統設計的用于把多個檔案系統聯合到同一個掛載點的檔案系統服務, 它能夠將不同檔案夾中的層聯合(Union)到了同一個檔案夾中,整個聯合的程序被稱為聯合掛載(Union Mount),

上圖是AUFS的實作,AUFS是作為Docker存盤驅動的一種實作,Docker 還支持了不同的存盤驅動,包括 aufs、devicemapper、overlay2、zfs 和 Btrfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成為了推薦的存盤驅動,但是在沒有 overlay2 驅動的機器上仍然會使用 aufs 作為 Docker 的默認驅動,
Docker網路
docker容器是一塊具有隔離性的虛擬系統,容器內可以有自己獨立的網路空間,
- 多個容器之間是如何實作通信的呢?
- 容器和宿主機之間又是如何實作的通信呢?
- 使用-p引數是怎么實作的埠映射?
帶著這些問題,我們來學習一下docker的網路模型,最后我會通過抓包的方式,給大家演示一下資料包在容器和宿主機之間的轉換程序,
網路模式
我們在使用docker run創建Docker容器時,可以用–net選項指定容器的網路模式,Docker有以下4種網路模式:
-
bridge模式,使用–net=bridge指定,默認設置
-
host模式,使用–net=host指定,容器內部網路空間共享宿主機的空間,效果類似直接在宿主機上啟動一個行程,埠資訊和宿主機共用
-
container模式,使用–net=container:NAME_or_ID指定
指定容器與特定容器共享網路命名空間
-
none模式,使用–net=none指定
網路模式為空,即僅保留網路命名空間,但是不做任何網路相關的配置(網卡、IP、路由等)
bridge模式
那我們之前在演示創建docker容器的時候其實是沒有指定的網路模式的,如果不指定的話默認就會使用bridge模式,bridge本意是橋的意思,其實就是網橋模式,
那我們怎么理解網橋,如果需要做類比的話,我們可以把網橋看成一個二層的交換機設備,我們來看下這張圖:
交換機通信簡圖

交換機網路通信流程:

網橋模式示意圖

Linux 中,能夠起到虛擬交換機作用的網路設備,是網橋(Bridge),它是一個作業在資料鏈路層(Data Link)的設備,主要功能是根據 MAC 地址將資料包轉發到網橋的不同埠上, 網橋在哪,查看網橋
$ yum install -y bridge-utils
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b5fbe57b no veth3a496ed
有了網橋之后,那我們看下docker在啟動一個容器的時候做了哪些事情才能實作容器間的互聯互通
Docker 創建一個容器的時候,會執行如下操作:
- 創建一對虛擬介面/網卡,也就是veth pair;
- veth pair的一端橋接 到默認的 docker0 或指定網橋上,并具有一個唯一的名字,如 vethxxxxxx;
- veth paid的另一端放到新啟動的容器內部,并修改名字作為 eth0,這個網卡/介面只在容器的命名空間可見;
- 從網橋可用地址段中(也就是與該bridge對應的network)獲取一個空閑地址分配給容器的 eth0
- 配置容器的默認路由
那整個程序其實是docker自動幫我們完成的,清理掉所有容器,來驗證,
## 清掉所有容器
$ docker rm -f `docker ps -aq`
$ docker ps
$ brctl show # 查看網橋中的介面,目前沒有
## 創建測驗容器test1
$ docker run -d --name test1 nginx:alpine
$ brctl show # 查看網橋中的介面,已經把test1的veth端接入到網橋中
$ ip a |grep veth # 已在宿主機中可以查看到
$ docker exec -ti test1 sh
/ # ifconfig # 查看容器的eth0網卡及分配的容器ip
# 再來啟動一個測驗容器,測驗容器間的通信
$ docker run -d --name test2 nginx:alpine
$ docker exec -ti test2 sh
/ # sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
/ # apk add curl
/ # curl 172.17.0.8:80
## 為啥可以通信?
/ # route -n #
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
# eth0 網卡是這個容器里的默認路由設備;所有對 172.17.0.0/16 網段的請求,也會被交給 eth0 來處理(第二條 172.17.0.0 路由規則),這條路由規則的網關(Gateway)是 0.0.0.0,這就意味著這是一條直連規則,即:凡是匹配到這條規則的 IP 包,應該經過本機的 eth0 網卡,通過二層網路(資料鏈路層)直接發往目的主機,
# 而要通過二層網路到達 test1 容器,就需要有 172.17.0.8 這個 IP 地址對應的 MAC 地址,所以test2容器的網路協議堆疊,就需要通過 eth0 網卡發送一個 ARP 廣播,來通過 IP 地址查找對應的 MAC 地址,

#這個 eth0 網卡,是一個 Veth Pair,它的一端在這個 test2 容器的 Network Namespace 里,而另一端則位于宿主機上(Host Namespace),并且被“插”在了宿主機的 docker0 網橋上,網橋設備的一個特點是插在橋上的網卡都會被當成橋上的一個埠來處理,而埠的唯一作用就是接收流入的資料包,然后把這些資料包的“生殺大權”(比如轉發或者丟棄),全部交給對應的網橋設備處理,
# 因此ARP的廣播請求也會由docker0來負責轉發,這樣網橋就維護了一份埠與mac的資訊表,因此針對test2的eth0拿到mac地址后發出的各類請求,同樣走到docker0網橋中由網橋負責轉發到對應的容器中,
# 網橋會維護一份mac映射表,我們可以大概通過命令來看一下,
$ brctl showmacs docker0
## 這些mac地址是主機端的veth網卡對應的mac,可以查看一下
$ ip a

我們如何知道網橋上的這些虛擬網卡與容器端是如何對應?
通過ifindex,網卡索引號
## 查看test1容器的網卡索引
$ docker exec -ti test1 cat /sys/class/net/eth0/ifindex
## 主機中找到虛擬網卡后面這個@ifxx的值,如果是同一個值,說明這個虛擬網卡和這個容器的eth0網卡是配對的,
$ ip a |grep @if
整理腳本,快速查看對應:
for container in $(docker ps -q); do
iflink=`docker exec -it $container sh -c 'cat /sys/class/net/eth0/iflink'`
iflink=`echo $iflink|tr -d '\r'`
veth=`grep -l $iflink /sys/class/net/veth*/ifindex`
veth=`echo $veth|sed -e 's;^.*net/\(.*\)/ifindex$;\1;'`
echo $container:$veth
done
上面我們講解了容器之間的通信,那么容器與宿主機的通信是如何做的?
添加埠映射:
## 啟動容器的時候通過-p引數添加宿主機埠與容器內部服務埠的映射
$ docker run --name test -d -p 8088:80 nginx:alpine
$ curl localhost:8088

埠映射如何實作的?先來回顧iptables鏈表圖

訪問本機的8088埠,資料包會從流入方向進入本機,因此涉及到PREROUTING和INPUT鏈,我們是通過做宿主機與容器之間加的埠映射,所以肯定會涉及到埠轉換,那哪個表是負責存盤埠轉換資訊的呢,就是nat表,負責維護網路地址轉換資訊的,因此我們來查看一下PREROUTING鏈的nat表:
$ iptables -t nat -nvL PREROUTING
Chain PREROUTING (policy ACCEPT 159 packets, 20790 bytes)
pkts bytes target prot opt in out source destination
3 156 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
規則利用了iptables的addrtype拓展,匹配網路型別為本地的包,如何確定哪些是匹配本地,
$ ip route show table local type local
127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
192.168.11.100 dev eth0 proto kernel scope host src 192.168.11.100
也就是說目標地址型別匹配到這些的,會轉發到我們的TARGET中,TARGET是動作,意味著對符合要求的資料包執行什么樣的操作,最常見的為ACCEPT或者DROP,此處的TARGET為DOCKER,很明顯DOCKER不是標準的動作,那DOCKER是什么呢?我們通常會定義自定義的鏈,這樣把某類對應的規則放在自定義鏈中,然后把自定義的鏈系結到標準的鏈路中,因此此處DOCKER 是自定義的鏈,那我們現在就來看一下DOCKER這個自定義鏈上的規則,
$ iptables -t nat -nvL DOCKER
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8088 to:172.17.0.2:80
此條規則就是對主機收到的目的埠為8088的tcp流量進行DNAT轉換,將流量發往172.17.0.2:80,172.17.0.2地址是不是就是我們上面創建的Docker容器的ip地址,流量走到網橋上了,后面就走網橋的轉發就ok了,
所以,外界只需訪問192.168.11.100:8088就可以訪問到容器中的服務了,
資料包在出口方向走POSTROUTING鏈,我們查看一下規則:
$ iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 1099 packets, 67268 bytes)
pkts bytes target prot opt in out source destination
86 5438 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.4 172.17.0.4 tcp dpt:80
大家注意MASQUERADE這個動作是什么意思,其實是一種更靈活的SNAT,把源地址轉換成主機的出口ip地址,那解釋一下這條規則的意思:
這條規則會將源地址為172.17.0.0/16的包(也就是從Docker容器產生的包),并且不是從docker0網卡發出的,進行源地址轉換,轉換成主機網卡的地址,大概的程序就是ACK的包在容器里面發出來,會路由到網橋docker0,網橋根據宿主機的路由規則會轉給宿主機網卡eth0,這時候包就從docker0網卡轉到eth0網卡了,并從eth0網卡發出去,這時候這條規則就會生效了,把源地址換成了eth0的ip地址,
注意一下,剛才這個程序涉及到了網卡間包的傳遞,那一定要打開主機的ip_forward轉發服務,要不然包轉不了,服務肯定訪問不到,
抓包演示
我們先想一下,我們要抓哪個網卡的包
-
首先訪問宿主機的8088埠,我們抓一下宿主機的eth0
$ tcpdump -i eth0 port 8088 -w host.cap -
然后最終包會流入容器內,那我們抓一下容器內的eth0網卡
# 容器內安裝一下tcpdump $ sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories $ apk add tcpdump $ tcpdump -i eth0 port 80 -w container.cap
到另一臺機器訪問一下,
$ curl 192.168.11.100:8088/
停止抓包,拷貝容器內的包到宿主機
$ docker cp test:/root/container.cap /root/
把抓到的內容拷貝到本地,使用wireshark進行分析,
$ scp root@192.168.11.100:/root/*.cap /d/packages
(wireshark合并包進行分析)


進到容器內的包做DNAT,出去的包做SNAT,這樣對外面來講,根本就不知道機器內部是誰提供服務,其實這就和一個內網多個機器公用一個外網IP地址上網的效果是一樣的,那這也屬于NAT功能的一個常見的應用場景,
Host模式
容器內部不會創建網路空間,共享宿主機的網路空間,比如直接通過host模式創建mysql容器:
$ docker run --net host -d --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
容器啟動后,會默認監聽3306埠,由于網路模式是host,因為可以直接通過宿主機的3306埠進行訪問服務,效果等同于在宿主機中直接啟動mysqld的行程,
Conatiner模式
這個模式指定新創建的容器和已經存在的一個容器共享一個 Network Namespace,而不是和宿主機共享,新創建的容器不會創建自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、埠范圍等,同樣,兩個容器除了網路方面,其他的如檔案系統、行程串列等還是隔離的,兩個容器的行程可以通過 lo 網卡設備通信,

## 啟動測驗容器,共享mysql的網路空間
$ docker run -ti --rm --net=container:mysql busybox sh
/ # ip a
/ # netstat -tlp|grep 3306
/ # telnet localhost 3306
在一些特殊的場景中非常有用,例如,kubernetes的pod,kubernetes為pod創建一個基礎設施容器,同一pod下的其他容器都以container模式共享這個基礎設施容器的網路命名空間,相互之間以localhost訪問,構成一個統一的整體,
None模式
只會創建對應的網路空間,不會配置網路堆疊(網卡、路由等),
# 創建none的容器
$ docker run -it --name=network-none --net=none nginx:alpine sh
# ifconfig
在宿主機中操作:
# 創建虛擬網卡對
$ ip link add A type veth peer name B
# A端插入到docker0網橋
$ brctl addif docker0 A
$ ip link set A up
# B端插入到network-none容器中,需要借助ip netns,因此需要顯示的創建命名network namespace
$ PID=$(docker inspect -f '{{.State.Pid}}' network-none)
$ mkdir -p /var/run/netns
$ ln -s /proc/$PID/ns/net /var/run/netns/$PID
# B端放到容器的命名空間
$ ip link set B netns $PID
$ ip netns exec $PID ip link set dev B name eth0 # 修改設備名稱為eth0,和docker默認行為一致
$ ip netns exec $PID ip link set eth0 up
# 設定ip
$ ip netns exec $PID ip addr add 172.17.0.100/16 dev eth0
# 添加默認路由,指定給docker0網橋
$ ip netns exec $PID ip route add default via 172.17.0.1
# 測驗容器間通信
前置知識:
- ip netns 命令用來管理 network namespace,它可以創建命名的 network namespace,然后通過名字來參考 network namespace
- network namespace 在邏輯上是網路堆疊的一個副本,它有自己的路由、防火墻規則和網路設備,
默認情況下,子行程繼承其父行程的 network namespace,也就是說,如果不顯式創建新的 network namespace,所有行程都從 init 行程繼承相同的默認 network namespace, - 根據約定,命名的 network namespace 是可以打開的 /var/run/netns/ 目錄下的一個物件,比如有一個名稱為 net1 的 network namespace 物件,則可以由打開 /var/run/netns/net1 物件產生的檔案描述符參考 network namespace net1,通過參考該檔案描述符,可以修改行程的 network namespace,
實用技巧
-
清理主機上所有退出的容器
$ docker rm $(docker ps -aq) -
除錯或者排查容器啟動錯誤
## 若有時遇到容器啟動失敗的情況,可以先使用相同的鏡像啟動一個臨時容器,先進入容器 $ docker run --rm -ti <image_id> sh ## 進入容器后,手動執行該容器對應的ENTRYPOINT或者CMD命令,這樣即使出錯,容器也不會退出,因為bash作為1號行程,我們只要不退出容器,該容器就不會自動退出
ip link add A type veth peer name B
# A端插入到docker0網橋
$ brctl addif docker0 A
$ ip link set A up
# B端插入到network-none容器中,需要借助ip netns,因此需要顯示的創建命名network namespace
$ PID=$(docker inspect -f '{{.State.Pid}}' network-none)
$ mkdir -p /var/run/netns
$ ln -s /proc/$PID/ns/net /var/run/netns/$PID
# B端放到容器的命名空間
$ ip link set B netns $PID
$ ip netns exec $PID ip link set dev B name eth0 # 修改設備名稱為eth0,和docker默認行為一致
$ ip netns exec $PID ip link set eth0 up
# 設定ip
$ ip netns exec $PID ip addr add 172.17.0.100/16 dev eth0
# 添加默認路由,指定給docker0網橋
$ ip netns exec $PID ip route add default via 172.17.0.1
# 測驗容器間通信
前置知識:
- ip netns 命令用來管理 network namespace,它可以創建命名的 network namespace,然后通過名字來參考 network namespace
- network namespace 在邏輯上是網路堆疊的一個副本,它有自己的路由、防火墻規則和網路設備,
默認情況下,子行程繼承其父行程的 network namespace,也就是說,如果不顯式創建新的 network namespace,所有行程都從 init 行程繼承相同的默認 network namespace, - 根據約定,命名的 network namespace 是可以打開的 /var/run/netns/ 目錄下的一個物件,比如有一個名稱為 net1 的 network namespace 物件,則可以由打開 /var/run/netns/net1 物件產生的檔案描述符參考 network namespace net1,通過參考該檔案描述符,可以修改行程的 network namespace,
實用技巧
-
清理主機上所有退出的容器
$ docker rm $(docker ps -aq) -
除錯或者排查容器啟動錯誤
## 若有時遇到容器啟動失敗的情況,可以先使用相同的鏡像啟動一個臨時容器,先進入容器 $ docker run --rm -ti <image_id> sh ## 進入容器后,手動執行該容器對應的ENTRYPOINT或者CMD命令,這樣即使出錯,容器也不會退出,因為bash作為1號行程,我們只要不退出容器,該容器就不會自動退出
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/330275.html
標籤:其他
下一篇:QT多執行緒實作UDP資料的發送

