DOCKER 04:容器資源限制和網絡原理

 

本文主要談談關於主機網絡和容器網絡的實現原理!node

 

容器資源限制python

 

在某些時候咱們不想讓容器肆無忌憚的搶佔系統資源,因此就會對其作一系列的限制,這些參數可使用蠻力查看到:git

docker container run --help

主要的限制參數包含如下這些:github

--cpu-shares:CPU 使用佔比,如一個容器配置 10,一個配置 20,另一個配置 10,那就是資源分配:1:2:1。redis

--memory:限制內存,好比配置 200M,可是若是不配置 swap,則實際內存實際上是 400M。docker

--memory-swap:限制 swap,結合內存限制使用。flask

 

 

虛擬機網絡名稱空間(選擇性瞭解)vim

 

docker 網絡隔離的實現方式其實就是網絡名稱空間的方式,可是這方面的知識其實對於就算是運維工程師也不必定有詳細的瞭解,因此這部份內容做爲選擇性瞭解。簡單的對其實現方式說明,而後看看效果是怎樣的。服務器

1. 創建兩個網絡名稱空間:網絡

# 查看現有的網絡名稱空間
ip netns ls

# 新建兩個網絡名稱空間
ip netns add net-ns-1
ip netns add net-ns-2

# 在兩個網絡名稱空間中分別查看它的網卡信息
ip netns exec net-ns-1 ip a
ip netns exec net-ns-2 ip a

結果如圖:

能夠看到各自擁有一張迴環網卡,而且未啓動。咱們能夠將其啓動起來再查看:

# 分別啓動網絡名稱空間中的迴環網卡
ip netns exec net-ns-1 ip link set dev lo up
ip netns exec net-ns-2 ip link set dev lo up

# 查看啓動效果
ip netns exec net-ns-1 ip a
ip netns exec net-ns-2 ip a

結果如圖:

此時狀態變成 UNKNOW,這就至關於一個機器,你至少一頭差了網線,另一端沒有插線的狀態。

 

2. 新建兩個虛擬網卡,並把它們綁定到命名空間中:

# 新建兩個網卡
ip link add veth-1 type veth peer name veth-2

結果如圖:

將網卡分別分配到兩個網絡名稱空間:

# 將網卡分別分配到指定的網絡名稱空間
ip link set veth-1 netns net-ns-1
ip link set veth-2 netns net-ns-2

# 查看當前虛擬機網卡信息
ip link

# 查看指定網絡名稱空間的網卡信息
ip netns exec net-ns-1 ip link
ip netns exec net-ns-2 ip link

結果如圖:

能夠發現本機中已經不存在這兩張網卡了,可是以前創建網絡名稱空間中能夠看到他們。

這樣的場景就相似於以前是一根網線的兩頭,咱們把它拔了下來,一頭插到了第一個網絡名稱空間的網口上,一頭插到了另外一個。

 

3. 給網口配置 IP 測試連通性:

# 配置 IP
ip netns exec net-ns-1 ip addr add 192.168.1.1/24 dev veth-1
ip netns exec net-ns-2 ip addr add 192.168.1.2/24 dev veth-2

# 啓動網卡
ip netns exec net-ns-1 ip link set dev veth-1 up
ip netns exec net-ns-2 ip link set dev veth-2 up

# 查看網卡信息
ip netns exec net-ns-1 ip a
ip netns exec net-ns-2 ip a

結果以下:

此時在網絡名稱空間之中測試網絡連通性:

ip netns exec net-ns-1 ping 192.168.1.2

結果如圖:

能夠發現網絡名空間之中由於兩個網卡至關於鏈接通的了效果,可是和宿主機自己倒是沒法通訊的。

這樣就實現了 docker 的明明本機卻可以作到網絡隔離的效果。固然 docker 並不是單單如此。

 

 

容器的網絡原理

 

docker 的網絡相較於上文的網絡模式多了一個 docker0 網橋,咱們能夠將其當作交換機:

並且因爲 iptables 的緣由,容器內部還可以上網!

查看容器的網絡:

docker network ls

結果如圖:

熟悉 VMware 的人就應該很熟悉這些:

bridge:橋接網絡,默認就是它,可以獨立網段於宿主機自己網絡,可是又能和宿主機所在的網絡其它主機通訊。

host:主機網絡,這意味着容器和宿主機將會共享端口,一樣的端口會致使端口衝突。

none:徹底獨立,沒法通訊。

 

由於這三種網絡的關係,意味着本機通屬於一個 bridge 的容器是能夠互相通訊的。能夠本身創建一個橋接網絡和兩個容器測試:

docker network create -d bridge my-bridge

結果如圖:

開兩個窗口,分別新建容器並加入到這個網絡:

# 窗口1
docker container run -it --name b3 --network my-bridge busybox /bin/sh

# 窗口2
docker container run -it --name b4 --network my-bridge busybox /bin/sh

查看窗口1 IP:

查看窗口2 IP:

窗口1 測試通訊:

窗口1 測試和宿主機通訊:

能夠發現可以和其它的宿主機通訊!可是沒法和其它宿主機的容器通訊,很簡單,由於他們是本身單獨的 IP,不說其它,單單是他們兩個就多是同樣的 IP 地址。

同時,咱們新開窗口查看網卡狀況:

能夠發現後面三個網卡其實就是相似於一個網絡鏈接,後面兩個都至關於一個網線,一頭會鏈接到 br-xxx 上面,一頭鏈接到容器中,因此它們可以通訊。

這個 br-xxx 其實就是咱們建立網絡的時候出現的。至於如何和其它宿主機通訊則是經過 iptables 的 nat 實現的。

查看網絡詳細信息:

docker network inspect my-bridge

結果以下:

能夠看到哪些容器屬於該網絡!

 

 

容器網絡名稱解析

 

因爲容器的 IP 是隨機的,因此在不少服務使用的時候咱們不能再依賴容器的的 IP 來期望鏈接到指定的容器。

固然這並不是不行,而是太麻煩並且不便於管理。那有沒有更好的辦法?有。

那就是咱們在建立容器時會指定容器的名稱,咱們看他經過解析這個名稱實現通訊。

這裏涉及到一個參數:--link,將一個容器鏈接到另一個容器。

1. 新建兩個容器,而後分別重新窗口進入容器測試網絡通訊:

# 建立容器
docker container run --name b1 -d busybox /bin/sh -c "while true;do sleep 3000;done"
docker container run --name b2 -d busybox /bin/sh -c "while true;do sleep 3000;done"

# 進入容器查看 IP 後測試網絡
docker container exec -it b1 /bin/sh

結果如圖:

能夠發現 ping IP 可以通訊,可是 ping 容器名稱不行。

 

2. 新建第三個容器,使用 link 綁定查看:

# 建立容器
docker container run --name b3 --link b1 -d busybox /bin/sh -c "while true;do sleep 3000;done"

# 進入容器
docker container exec -it b3 /bin/sh

測試連通性如圖:

能夠看到在 b3 中可以經過容器名稱 ping 通 b1,可是沒法 ping 通 b2。


3. 進入 b1 中測試和 b3 的連通性:

docker container exec -it b1 /bin/sh

結果如圖:

沒法通,由此能夠得出一個結論:--link 在綁定的時候是單向解析的。

這樣就解決了咱們想要經過容器名稱來實現服務之間的通訊而不是依靠隨時均可能改變的 IP 地址。

 

 

容器端口映射

 

將容器內部咱們須要的某個端口映射到宿主機,這算是一種解決辦法,由於宿主機的 IP 是固定的。

這裏涉及到兩個參數:

-p:將宿主機的某個端口映射到容器的某個端口。

-P:將容器的全部端口隨機映射到宿主機的隨機端口。

 

1. 爲了便於理解,這裏仍是以以前的 Flask 項目爲例,不過這裏咱們改一下代碼,讓其複雜一點。

mkdir flask-redis-demo
cd flask-redis-demo/
vim app.py

內容以下:

from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', "127.0.0.1"), port=6379)

@app.route("/")
def hello():
    redis.incr('hits')
    return 'Redis hits: %s, Hostname: %s' % (redis.get('hits'), socket.gethostname())

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

其實就是 flask 經過用戶傳遞過來的變量找到 redis 的地址鏈接上去,而後獲取到 redis 的 hits 打印出來。

這就涉及到跨容器通訊!

 

2. 編寫 Dockerfile:

FROM python:2.7
LABEL author="Dylan" mail="1214966109@qq.com"
RUN pip install flask && pip install redis
COPY app.py /app/
WORKDIR /app/
EXPOSE 5000
CMD ["python", "app.py"]

製做成爲鏡像:

docker image build -t dylan/flask-redis-demo .

 

3. 先運行一個 redis 容器:

docker container run -d --name redis-demo redis

結果如圖:

 

4. 此時運行 flask 容器並鏈接到 redis-demo 容器:

docker container run -d --name flask-redis-demo --link redis-demo -e REDIS_HOST="redis-demo" dylan/flask-redis-demo

這裏有個新增的參數:-e 傳遞變量給容器,結果如圖:

此時咱們在容器中訪問測試:

docker container exec -it flask-redis-demo curl 127.0.0.1:5000

輸入以下:

Redis hits: 1, Hostname: 0a4f4560fa72

能夠看到容器內部是正常的,而且 flask 容器能夠去 redis 容器中獲取值。可是問題仍是在於該地址只能容器內部訪問。

 

5. 新建容器,將端口隨機映射出來:

docker container run -d --name flask-redis-demo-1 -P --link redis-demo -e REDIS_HOST="redis-demo" dylan/flask-redis-demo

結果如圖:

能夠看到宿主機的 1025 端口映射到了容器內部的 5000 端口,此時咱們訪問本機 1025 端口測試:

curl 192.168.200.101:1025

結果如圖:

 

6. 新建容器,將容器端口映射到 8000 端口:

docker container run -d --name flask-redis-demo-2 --link redis-demo -e REDIS_HOST="redis-demo" -p 8000:5000 dylan/flask-redis-demo

結果如圖:

訪問測試:

至此,端口映射完成!

 

 

跨主機通訊

 

雖然端口映射可以解決咱們必定量的問題,可是在面對某些複雜的狀況的時候也會很麻煩。好比跨主機通訊。

這就不是 link 可以解決的問題,須要將全部用到的服務的端口都映射到宿主機才能完成操做!

那有沒有辦法將多個主機的容器作成相似於一個集羣的樣子?這就是涉及容器的跨主機通訊問題。

這一次咱們會用到兩個虛擬機,192.168.200.101 和 102,都安裝好 docker。

在實現這個功能以前,咱們須要瞭解到兩個東西:

overlay 網絡:至關於在兩個機器上面創建通訊隧道,讓兩個 docker 感受運行在一個機器上。

etcd:基於 zookeeper 的一個共享配置的 KV 存儲系統。其中 etcd 地址以下:

https://github.com/etcd-io/etcd/tags

 

在使用以前須要注意:

1. 服務器的主機名最好具備必定的意義,最簡單也要坐到惟一,一樣的主機名可能出現 BUG。

2. 確認關閉防火牆這些東西。

3. 能有 epel 源最好。

yum -y install epel-release

我在安裝的時候,epel 源中最新的 etcd 版本爲:3.3.11-2

 

1. 在 101 上面安裝 etcd 並配置:

yum -y install etcd
cd /etc/etcd/
cp etcd.conf etcd.conf_bak
vim etcd.conf

內容以下:

#[Member]
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"
ETCD_NAME="docker-node-01"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.200.101:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.200.101:2379,http://192.168.200.101:4001"
ETCD_INITIAL_CLUSTER="docker-node-01=http://192.168.200.101:2380,docker-node-02=http://192.168.200.102:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

注意紅色的地方,名稱保持一致。

同理在 102 上面也進行相似的配置:

#[Member]
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"
ETCD_NAME="docker-node-02"

#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.200.102:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.200.102:2379,http://192.168.200.102:4001"
ETCD_INITIAL_CLUSTER="docker-node-01=http://192.168.200.101:2380,docker-node-02=http://192.168.200.102:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

注意修改 IP 地址便可。

關鍵字 說明
ETCD_DATA_DIR 數據存儲目錄
ETCD_LISTEN_PEER_URLS 與其餘節點通訊時的監聽地址列表,通訊協議能夠是http、https
ETCD_LISTEN_CLIENT_URLS 與客戶端通訊時的監聽地址列表
ETCD_NAME 節點名稱,在也就是後面配置的那個 名字=地址
ETCD_INITIAL_ADVERTISE_PEER_URLS 節點在整個集羣中的通訊地址列表,能夠理解爲能與外部通訊的ip端口
ETCD_ADVERTISE_CLIENT_URLS 告知集羣中其餘成員本身名下的客戶端的地址列表
ETCD_INITIAL_CLUSTER 集羣內全部成員的地址,這就是爲何稱之爲靜態發現,由於全部成員的地址都必須配置
ETCD_INITIAL_CLUSTER_TOKEN 初始化集羣口令,用於標識不一樣集羣
ETCD_INITIAL_CLUSTER_STATE 初始化集羣狀態,new表示新建

 

2. 啓動服務:

systemctl start etcd
systemctl enable etcd

# 查看節點
etcdctl member list

# 監控檢查
etcdctl cluster-health

結果如圖:

能夠看到集羣處於健康狀態!

 

3. 此時修改 101 的 docker 啓動配置:

# 中止 docker
systemctl stop docker

# 修改配置
vim /etc/systemd/system/docker.service

修改內容以下:

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --registry-mirror=https://jxus37ad.mirror.aliyuncs.com -H tcp://0.0.0.0:2375 --cluster-store=etcd://192.168.200.101:2379 --cluster-advertise=192.168.200.101:2375

主要增長了紅色部分,而後啓動 docker:

systemctl daemon-reload
systemctl start docker

同理 102 也進行修改,注意 IP 地址:

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --registry-mirror=https://jxus37ad.mirror.aliyuncs.com -H tcp://0.0.0.0:2375 --cluster-store=etcd://192.168.200.102:2379 --cluster-advertise=192.168.200.102:2375

一樣重啓 docker,此時的 docker 進程發生了改變:

ps -ef | grep docker

結果以下:

 

4. 此時在 101 上建立 overlay 網絡:

docker network create -d overlay my-overlay

結果以下:

此時查看 102 網絡:

已經同步過來了,若是沒有同步成功,能夠從新關閉 102 的防火牆,重啓 102 的 docker 再看。

 

5. 此時在 101 和 102 上面分別建立容器而後測試網絡的連通性:

# 101 上面建立
docker container run -d --name b1 --network my-overlay busybox /bin/sh -c "while true;do sleep 3000;done"

# 102 上面建立
docker container run -d --name b2 --network my-overlay busybox /bin/sh -c "while true;do sleep 3000;done"

注意這裏新的參數:--network,指定容器的網絡,再也不是默認的 bridge 網絡。

docker container exec -it b1 ping b2

直接在 b1 上面 ping b2:

發現已經可以實現正常的通訊了。並且再也不須要咱們指定 link 參數了。

固然 etcd 也有它的缺點,它是靜態發現,意味着咱們增長節點就須要增長配置和重啓 etcd。

相關文章
相關標籤/搜索