@(上海義為實習)
## Docker Swarm
定稿人 | 定稿日期 | 系統環境
| :--------: | :-----: | :----: |
李程&黃鎮游 | 2017.11.28 | centos7 + docker1.13 + docker-compose1.16
### Swarm背景
現實中我們的應用可能會有很多,應用本身也可能很復雜,單個Docker Engine所能提供的資源未必能夠滿足要求。而且應用本身也會有可靠性的要求,希望避免單點故障,這樣的話勢必需要分布在多個Docker Engine。在這樣一個大背景下,Docker社區就產生了Swarm項目。
### Swarm簡介
Swarm這個項目名稱特別貼切。在Wiki的解釋中,Swarm behavior是指動物的群集行為。比如我們常見的蜂群,魚群,秋天往南飛的雁群都可以稱作Swarm behavior。

Swarm項目正是這樣,通過把多個Docker Engine聚集在一起,形成一個大的docker-engine,對外提供容器的集群服務。同時這個集群對外提供Swarm API,用戶可以像使用Docker Engine一樣使用Docker集群。
### Swarm 特點
- 對外以Docker API接口呈現,這樣帶來的好處是,如果現有系統使用Docker Engine,則可以平滑將Docker Engine切到Swarm上,無需改動現有系統。
- Swarm對用戶來說,之前使用Docker的經驗可以繼承過來。非常容易上手,學習成本和二次開發成本都比較低。同時Swarm本身專注于Docker集群管理,非常輕量,占用資源也非常少。 *“Batteries included but swappable”,簡單說,就是插件化機制,Swarm中的各個模塊都抽象出了API,可以根據自己一些特點進行定制實現。
- Swarm自身對Docker命令參數支持的比較完善,Swarm目前與Docker是同步發布的。Docker的新功能,都會第一時間在Swarm中體現。
### Swarm 集群管理
1. 在管理節點上初始化swarm
```powershell
[root@master docker-spark-master]# docker swarm init
Swarm initialized: current node (n7o0o1xl5q4r7lahggc5hseju) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-1ujkotzvkhpvrzwq3hstwpcxsrf84taz47wzyzju0atapcxdd7-4ciso6e5ox3mi7tgraobuo9tu 192.168.235.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```
2. 將所有worker節點加入到該swarm集群中
```
# 在所有worker節點
docker swarm join --token SWMTKN-1-1ujkotzvkhpvrzwq3hstwpcxsrf84taz47wzyzju0atapcxdd7-4ciso6e5ox3mi7tgraobuo9tu 192.168.235.100:2377
```
3. 在管理節點上查看集群各節點狀態
```
[root@master docker-spark-master]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
n7o0o1xl5q4r7lahggc5hseju * master Ready Active Leader
0gbvgkqz99q1ooc92yvj0ygoi slave1 Ready Active
maysdjlg2zin9lfzztjvnln98 slave2 Ready Active
```
到這一步我們docker swarm就搭建成功了,接下來要做的就是在集群上跑服務了
但在此之前我們補充一些知識點,那就是docker網絡與服務發現。
### Docker 網絡
#### docker network ls
這個命令用于列出所有當前主機上或Swarm集群上的網絡:
```
$ docker network ls
NETWORK ID NAME DRIVER
6e6edc3eee42 bridge bridge
1caa9a605df7 none null
d34a6dec29d3 host host
```
在默認情況下會看到三個網絡,它們是Docker Deamon進程創建的。它們實際上分別對應了Docker過去的三種『網絡模式』:
- bridge:容器使用獨立網絡Namespace,并連接到docker0虛擬網卡(默認模式)
- none:容器沒有任何網卡,適合不需要與外部通過網絡通信的容器
- host:容器與主機共享網絡Namespace,擁有與主機相同的網絡設備
在docker1.9版本引入libnetwork后,它們不再是固定的『網絡模式』了,而只是三種不同『網絡插件』的實體。說它們是實體,是因為現在用戶可以利用Docker的網絡命令創建更多與默認網絡相似的網絡,每一個都是特定類型網絡插件的實體。
由此可以看出,libnetwork帶來的最直觀變化實際上是:docker0不再是唯一的容器網絡了,用戶可以創建任意多個與docker0相似的網絡來隔離容器之間的通信。然而,要仔細來說,用戶自定義的網絡和默認網絡還是有不一樣的地方。
默認的三個網絡是不能被刪除的,而用戶自定義的網絡可以用『docker networkrm』命令刪掉;
**連接到默認的bridge網絡連接的容器需要明確的在啟動時使用『–link』參數相互指定,才能在容器里使用容器名稱連接到對方。而連接到自定義網絡的容器,不需要任何配置就可以直接使用容器名連接到任何一個屬于同一網絡中的容器**。這樣的設計即方便了容器之間進行通信,又能夠有效限制通信范圍,增加網絡安全性;
在Docker 1.9文檔中已經明確指出,**不再推薦容器使用默認的bridge網卡**,它的存在僅僅是為了兼容早期設計。而容器間的『–link』通信方式也已經被標記為『過時的』功能,并可能會在將來的某個版本中被徹底移除。
在Docker的1.9中版本中正式加入了官方支持的跨節點通信解決方案,而這種內置的跨節點通信技術正是使用了Overlay網絡的方法。
#### Overlay網絡的特點
swarm模式的overlay network有以下特征:
- 支持多個跨節點服務連接到同一個網絡中。
- 默認情況下,Overlay網絡的服務發現機制給swarm集群中的每個服務都分配一個虛擬IP地址和一個DNS條目,所以在同一網絡中的任何一個容器可以通過服務名稱跨節點訪問其他服務。
#### Swarm服務發現
將容器應用部署到集群時,其服務地址,即IP和端口, 是由集群系統動態分配的。那么,當我們需要訪問這個服務時,如何確定它的地址呢?這時,就需要服務發現(Service Discovery)了。
官方推薦是使用Etcd、ZooKeeper、Consul等第三方插件解決swarm集群的服務發現,而我們為了方便,使用了Overlay固定主機名的方法變相的實現了swarm集群的服務發現。
### 實戰:使用swarm啟動spark+hdfs+yarn集群
#### 創建overlay網絡
```
docker network create -d overlay hadoopv1
```
#### 創建本地鏡像庫
如果我們使用swarm來啟動服務,那么各種服務是分布在很多不同服務器節點上的,難道每個節點都要build所需要的鏡像,或者說把image拷貝到所有節點上?
當然不用,這個時候我們要用到本地鏡像倉庫docker registry,用于保存images,每個節點首先在本地尋找目標鏡像,如果沒有的就去本地鏡像拉取所需要的image即可。
##### 部署鏡像庫
1. 啟動本地鏡像庫服務
```
docker service create --name registry --publish 5000:5000 \
--mount type=bind,source=/root/registry,destination=/var/lib/registry \
--constraint 'node.hostname==master' registry:2
```
> - mount參數將鏡像存儲目錄映射出宿主機,實現服務啟動后鏡像庫不丟失鏡像,source是宿主機路徑,des為容器內路徑。
> - constraint參數將該服務固定在某臺主機上,這樣所有的鏡像都可以使用一個固定的地址。
2. 修改所有節點的的配置文件
```
1. 創建或者修改 /etc/docker/daemon.json 文件,添加下面一行
{ "insecure-registries":["master:5000"] }
2. 重啟docker
sudo /etc/init.d/docker restart
```
3. 驗證registry服務工作是否正常
`curl master:5000/v2/_catalog`
返回{"repositories":[]} 說明registry服務工作正常.
##### 測試本地鏡像倉庫
1. 首先需要將想要上傳的鏡像重命名為`<registry>/<image name>:<tag>`:
```
[root@node01 ~]# docker tag hadoop:v1 master:5000/hadoop:v1
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID
...
hadoop v1 4e38e38c8ce0
master:5000/hadoop v1 4e38e38c8ce0
```
2. 上傳鏡像
```
[root@node01 ~]# docker push master:5000/hadoop:v1
The push refers to a repository [master:5000/hadoop:v1]
4fe15f8d0ae6: Pushed
latest: digest: sha256:dc89ce8401da81f24f7ba3f0ab2914ed9013608bdba0b7e7e5d964817067dc06
```
3. 查看上傳鏡像
```
[root@master ~]# curl master:5000/v2/_catalog
{"repositories":["hadoop"]}
```
可以看到hadoop鏡像,已經在我們的本地鏡像庫中了。
4. 使用鏡像庫
上傳完之后就可以在該集群的任何主機上使用該鏡像啦。
```
version: "3"
services:
namenode:
image: master:5000/hadoop:v1
```
#### 使用swarm集群啟動docker-compse編排后的hadoop+hdfs集群
> 注:spark historyserver存儲和展示的路徑`hdfs:/spark/history`需要提前創建。
```
version: "3"
services:
namenode:
image: master:5000/hadoop:v1
command: [
"hdfs",
"namenode",
"-Dfs.defaultFS=hdfs://0.0.0.0:9000",
"-Ddfs.namenode.name.dir=/root/hadoop/dfs/name",
"-Ddfs.replication=3",
"-Ddfs.namenode.datanode.registration.ip-hostname-check=false",
"-Ddfs.permissions.enabled=false",
"-Ddfs.namenode.safemode.threshold-pct=0"
]
hostname: namenode
ports:
- "50070:50070"
networks:
- hadoopv1
volumes:
- /root/hadoop/dfs/name:/root/hadoop/dfs/name
# 將namenode服務綁定在集群中某一個主機上
deploy:
placement:
constraints:
- node.hostname == master
datanode:
image: master:5000/hadoop:v1
command: [
"hdfs",
"datanode",
"-fs", "hdfs://namenode:9000",
"-Ddfs.datanode.data.dir=/root/hadoop/dfs/data",
"-Ddfs.permissions.enabled=false"
]
# 創建3各節點,按照每10s創建2各節點的速度擴展
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
networks:
- hadoopv1
master:
image: master:5000/spark:2.2.0
command: bin/spark-class org.apache.spark.deploy.master.Master -h master
hostname: master
environment:
MASTER: spark://master:7077
SPARK_MASTER_OPTS: "-Dspark.eventLog.dir=hdfs://namenode:9000/spark/history"
SPARK_PUBLIC_DNS: master
ports:
- 8080:8080
networks:
- hadoopv1
deploy:
placement:
constraints:
- node.hostname == master
worker:
image: master:5000/spark:2.2.0
command: bin/spark-class org.apache.spark.deploy.worker.Worker spark://master:7077
environment:
SPARK_WORKER_CORES: 1
SPARK_WORKER_MEMORY: 1g
SPARK_WORKER_PORT: 8881
SPARK_WORKER_WEBUI_PORT: 8081
SPARK_PUBLIC_DNS: master
networks:
- hadoopv1
deploy:
replicas: 2
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
historyServer:
image: master:5000/spark:2.2.0
command: bin/spark-class org.apache.spark.deploy.history.HistoryServer
hostname: historyServer
environment:
MASTER: spark://master:7077
SPARK_PUBLIC_DNS: master
SPARK_HISTORY_OPTS: "-Dspark.history.ui.port=18080 -Dspark.history.retainedApplications=3 -Dspark.history.fs.logDirectory=hdfs://namenode:9000/spark/history"
ports:
- 18080:18080
networks:
- hadoopv1
deploy:
restart_policy:
condition: on-failure
placement:
constraints:
- node.hostname == master
ResourceManager:
image: master:5000/hadoop:v1
command: [
"yarn",
"resourcemanager",
"-Dfs.defaultFS=hdfs://namenode:9000",
"-Dyarn.nodemanager.aux-services=mapreduce_shuffle",
"-Ddfs.namenode.datanode.registration.ip-hostname-check=false",
"-Ddfs.permissions.enabled=false -Dmapreduce.framework.name=yarn",
"-Dyarn.resourcemanager.webapp.address=0.0.0.0:8088"
]
hostname: ResourceManager
ports:
- "8088:8088"
networks:
- hadoopv1
deploy:
restart_policy:
condition: on-failure
placement:
constraints:
- node.hostname == master
nodemanager:
image: master:5000/hadoop:v1
command: [
"yarn",
"nodemanager",
"-Dyarn.resourcemanager.hostname=ResourceManager",
"-Dyarn.nodemanager.resource.memory-mb=1024",
"-Dyarn.nodemanager.aux-services=mapreduce_shuffle",
"-Ddfs.namenode.datanode.registration.ip-hostname-check=false",
"-Dmapreduce.framework.name=yarn",
"-Ddfs.permissions.enabled=false"
]
networks:
- hadoopv1
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
networks:
hadoopv1:
driver: overlay
```
> 注意:Swarm只支持docker-compose v3版本。
編排好各集群服務后,我們就可以使用docker swarm來部署啦
```
# swarm啟動服務
$ docker stack deploy -c docker-compose.yml <stack name>
# 查看服務列表和狀態
$ docker service ls
# 查看具體某一個服務的狀態(運行在哪一臺主機上,狀態如何)
$ docker service ps <service name>
# 刪除服務
$ docker stack rm <service name>
```
```
[root@master docker-spark-master]# docker stack deploy -c docker-compose.yml test1
Creating network test1_hadoopv1
Creating service test1_namenode
Creating service test1_datanode
Creating service test1_master
Creating service test1_worker
Creating service test1_historyServer
Creating service test1_ResourceManager
Creating service test1_nodemanager
[root@master docker-spark-master]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
8njt9ve5uat8 test1_ResourceManager replicated 1/1 hadoop:v1 *:8088->8088/tcp
m189redu2ta9 test1_datanode replicated 3/3 hadoop:v1
ot1odc92qt3g test1_historyServer replicated 1/1 spark:2.2.0 *:18080->18080/tcp
l6gre8dddwjy test1_master replicated 1/1 spark:2.2.0 *:8080->8080/tcp
1zw4vnxwn3gt test1_namenode replicated 1/1 hadoop:v1 *:50070->50070/tcp
q9hwdeqqyazo test1_nodemanager replicated 2/3 hadoop:v1
rcx81uciwrps test1_worker replicated 2/2 spark:2.2.0
[root@master docker-spark-master]# docker service ps test1_datanode
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
49krv0ckjn1a test1_datanode.1 hadoop:v1 master Running Running 2 minutes ago
zfj1k00ue8gg test1_datanode.2 hadoop:v1 slave1 Running Running 2 minutes ago
3eq1nhrb8qae test1_datanode.3 hadoop:v1 slave2 Running Running 2 minutes ago
```
既然服務已經全部啟動了,那接下來我們就要在集群上跑程序測試啦
####在swarm集群運行spark程序
> 由于swarm集群不能和宿主機實現互通,因此跑任務時將程序封裝成鏡像單獨啟動一個容器,在swarm集群中運行。
```
$ docker service create --restart-condition=none --network hadoopv1 master:5000/spark:v1 \
--master spark://master:7077 --class org.apache.spark.examples.SparkPi ./examples/jars/spark-examples_2.11-2.0.2.jar
# or
$ docker service create --name spark_job \
--entrypoint "bin/spark-submit --class org.apache.spark.examples.SparkPi --master spark://master:7077 examples/jars/spark-examples_2.11-2.2.0.jar 10" \
--network hadoopv1 --restart-condition none master:5000/spark:v1
# 由于server會循環執行,故添加[--restart-condition=none]
```
> 由于restart-condition參數默認是any,這樣會造成程序執行完重復執行,所以這里設置為none,表示容器退出后不重新啟動。
#### 在swarm集群運行spark on yarn 程序
> 由于該程序需要spark、hadoop環境,因此封裝鏡像時需要配置這兩個環境
```powershell
$ docker service create --name yarn_job \
-e HADOOP_CONF_DIR=/usr/hadoop/etc/hadoop \
--entrypoint "bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn --deploy-mode cluster --driver-memory 512M --executor-memory 512M examples/jars/spark-examples_2.11-2.2.0.jar 10" \
--network hadoopv1 --restart-condition none \
[有程序環境的鏡像名]
```
至此我們使用swarm搭建hadoop+spark大數據集群任務已經全部完成。
> 注:使用swarm搭建大數據集群存在一個難以解決的問題就是需要掛載文件系統的服務不支持一鍵擴展,因為一個宿主機掛載點不支持同時被多個服務掛載。比如說對于datanode來說,如果想要擴展節點,只能一個個手動設置不同的掛載目錄,這樣才能實現數據的持久化。如果想要解決這個問題,可以嘗試使用Kubernetes。
- Docker
- Docker入門
- docker管理UI
- 封裝各大數據組件
- 自主封裝
- 封裝hadoop
- 封裝spark
- 官方封裝
- 封裝hue
- 封裝jenkins
- Swarm
- Swarm入門
- Zookeeper on swarm
- Hue on swarm
- Grafana
- influxDB
- Prometheus
- cAdvisor
- kubernetes
- k8s入門
- k8s部署dashboard
- minikube
- 手動搭建k8s的高可用集群
- 01環境準備
- 02部署etcd集群
- 03配置kubelet
- 04部署flannel網絡
- 05部署master集群
- 06配置高可用
- 07部署node節點
- 08驗證集群
- Monitor
- swarm 監控
- influxDB+Grafana
- Prometheus+Grafana