docker部署Elasticsearch集群

安装 docker

安装docker 查看官方文档:针对每个系统版本进行安装。

https://docs.docker.com/installation/#installation

启动 docker

$ sudo service docker start

编写Dockerfile

FROM     debian:jessie
MAINTAINER Sunny "[email protected]"
RUN apt-get update

RUN apt-get install -y vim wget curl
ENV Elasticsearch_version elasticsearch-1.7.0
RUN echo "root:root" |chpasswd

RUN 
  echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list && 
  echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list && 
  apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EEA14886 && 
  apt-get update

RUN 
  echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && 
  apt-get install -y oracle-java8-installer &&
  apt-get clean

RUN echo "JAVA_HOME=/usr/lib/jvm/java-8-oracle" >> /etc/environment

RUN mkdir /usr/es
WORKDIR /usr/es
RUN wget https://download.elastic.co/elasticsearch/elasticsearch/$Elasticsearch_version.tar.gz
RUN tar -zxf $Elasticsearch_version.tar.gz
WORKDIR $Elasticsearch_version/config
RUN mv elasticsearch.yml elasticsearch.yml.bak
WORKDIR /usr/es
EXPOSE 9200 9300

根据Dockerfile制作 images

Dockerfile文件路径:docker/

sudo docker build -t debian/elasticsearch .
编译完成后查看镜像
$ sudo docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
debian/elasticsearch         latest              5f0668a6b9k0        About an hour ago   899.1 MB

安装weave

sudo wget -O /usr/local/bin/weave https://raw.githubusercontent.com/zettio/weave/master/weave
sudo chmod a+x /usr/local/bin/weave
weave launch && weave launch-dns && weave launch-proxy
sudo weave launch
sudo weave ps 查看weave的路由状态
sudo weave status 查看weave状态
sudo weave expose <addr> 添加IP,能够使主机访问container

weave 启动container

在物理机A(192.168.1.135)上启动Container

sudo weave run 10.0.0.1/24 --name "dataNode1" -m 4g -v /opt/conf/data_node_1:/usr/tomcat/conf -v /opt/data/data_node_1:/usr/tomcat/data debian/tomcat

在物理机B(192.168.1.136)上启动Container

sudo weave run 10.0.0.2/24 --name "dataNode2" -m 4g -v /opt/conf/data_node_2:/usr/tomcat/conf -v /opt/data/data_node_2:/usr/tomcat/data debian/tomcat

在物理机A(192.168.1.135)上执行

sudo weave launch

在物理机B(192.168.1.136)上执行

sudo weave launch 192.168.1.136
sudo weave connect 192.168.1.135

weave实现container跨主机互联需要注意的地方

注意:

1、weave 的应用隔离功能

不同子网的container之间是默认隔离的,即使他们在同一台机器上面也不能ping通,不同物理机之间的容器也是默认隔离的。 所以在weave启动container的时候 集群应用的IP必须在同一个字网下面。10.0.0.1,10.0.0.2,而不能是10.0.0.1,10.0.1.1.

2、当container关闭需要重新启动的时候

建议用weave启动container。
这时候必须制定addr,否则ip会改变。

sudo weave start 10.0.0.1/24 node1
sudo weave start 10.0.0.2/24 node2
sudo weave start 10.0.0.3/24 node3
sudo weave start 10.0.0.4/24 node4

3、宿主机器与container通信的时候,需要执行下面的语句:

sudo weave expose <addr> 添加IP,能够使主机访问container
此处的addr必须是没有被使用的addr。
比如:
sudo weave expose 10.0.0.254/24
这个10.0.0.254/24地址必须是没有被使用的。

4、使用sudo weave expose 10.0.0.254/24 这种方式能够使宿主机和container通信,但是当宿主机重新启动的时候这个值就不存在了,建议开机启动的时候就设置这个值。

weave 数据传输图

(此图是转载的)

未分类

从图中可以看到容器之间是怎么跨主机通信的,性能问题可能就住要集中在weave Router上面了

配置Elasticsearch

elasticsearch.yml 内容如下

cluster.name: aaaaaaa-name
node.name: nade_01
index.number_of_shards: 8
index.number_of_replicas: 2
network.host: 10.0.0.1
transport.tcp.port: 9300
transport.tcp.compress: true
path.data: /usr/es/data
path.logs: /usr/es/logs
http.port: 9200
http.enabled: true
node.master: true
node.data: true
index.store.type: mmapfs
discovery.zen.minimum_master_nodes: 2
discovery.zen.ping.timeout: 90s
indices.fielddata.cache.size: 40%
bootstrap.mlockall: true
gateway.expected_nodes: 4
indices.recovery.max_size_per_sec: 100mb
index.cache.field.type: soft
node.rack_id: 101150
cluster.routing.allocation.awareness.attributes: rack_id

配置事项:

1、indices.fielddata.cache.size 这个值如果不用doc_values的话最好设置一下,默认是无上限的。 不过最好启用doc_values ,Elasticsearch官方也说明在以后的版本中doc_values会成为默认值。

2、因为只有两台物理机,并且配置也不一样所以设置了一下分片规则。

关闭swap

由于swap会影响Elasticsearch的性能,所以应该关闭swap

请看Es官方提供的方法: https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html

注意:官网上说的方法不能在容器里面执行,因为swap是系统功能,应该在宿主机上禁用swap。 docker 容器里面的root的用户如果不是特权容器是没有办法修改这些参数的。还有就是容器最好不要用特权启动,那样容器里面的root和外面的root权限就一样了,很危险。

修改fd 参数

和swap修改的方式一样,需要在宿主机上修改, sysctl -w vm.max_map_count=262144 这个事官网修改的值(没有测试),要想彻底修改这个值需要修改/etc/sysctl.conf这个文件,在里面加上vm.max_map_count=262144

启动容器

因为要用weave实现cluster,所以容器用weave启动

sudo weave run 10.0.0.1/24 --name "dataNode1" -m 9g -v /opt/conf/data_node_1:/usr/es/elasticsearch-1.7.0/config -v /opt/data/data_node_1:/usr/es/data -v /opt/logs/data_node_1:/usr/es/logs debian/elasticsearch /usr/es/elasticsearch-1.7.0/bin/elasticsearch -D

注意:要把es三个目录挂在外面,配置文件目录、数据目录、日志目录。

这样用docker结合weave 就能实现跨主机的cluster。

使用Docker构建基于Jenkins的GCC编译环境Slave

第一篇有点干货的文章,虽然没什么技术含量但还是写一篇方便有相同需求的人快速搭出一个环境吧。

提前准备

基本的Jenkins Docker Git 和 Linux 知识。
一台运行了Docker的x86设备。

0x1配置Docker

首先将[Dockerfile]和运行脚本下载下来,接着build镜像。镜像建好之后,从该镜像新建一个容器。如果你的 Jenkins 不是运行在 Docker 里的话。那么建议添加-p的端口映射指令,将22端口映射到主机的某个 Docker ,使 Jenkins 能够访问得到。

0x2 配置Jenkins

打开 Jenkins,登入。在主界面点击 Manage Jenkins -> Manage Nodes。

未分类

之后点击左边的 New Node -> 选择 Permanent Agent 输入完名称之后点击 OK。在接来下出现的设置对话框中,我们需要注意的有这么几个选项。

  1. # of executors -> 这个决定了该节点能够同时运行的任务数量,如果你的编译脚本或者说编译任务没有对多线程CPU进行优化的话, 那么我建议设置成 CPU个数 * 核心数。

  2. Remote root directory -> 这个决定了该节点的工作目录。可以配合Docker的挂载目录使用。

  3. Usage -> 这个需要根据个人需求来做设置了。默认有两个选项一个是仅执行指定分配的任务,另外一个是尽可能的执行任务。我自己的设置是仅执行指定分配的任务,因为该节点是只用来编译测试x86环境的。所以只运行需要在x86环境上测试的任务。

  4. Launch method -> 这个是选项Jenkins启动节点的方式,这里选择用SSH(需要安装SSH连接slave的插件)。Host是slave的ip(Jenkins和slave运行在同一个Docker,那么你需要查看一下运行slave容器的所分配到的IP)点击 Advanced 可以选择配置端口,添加好ssh的帐号密码 (默认密码查看slave的Dockerfile注释),以及根据个人的需求选择下放的 Host Key 验证方式。

设置完成之后就可以点击Save了。那么此时我们可以看到节点虽然添加成功了但是显示的是offline。不过如果一切正常的话,点击Launch agent就能够上线了。

未分类

0x3 测试

我这里随便选择了一个git上的项目作为测试用。具体可以参考一下配图。需要注意的是我之前节点设置的是只执行分配到节点的任务。所以在设置job的时候,需要勾上 Restrict where this project can be run 然后输入node的名称。这样构建脚本的时候才能slave才能执行。

未分类

未分类

添加完成之后点击构建,一切顺利的话就能够正常编译了。

未分类

Jenkins+Github+Docker实现自动化构建运行Jar

介绍

  • docker:简单的说,如果你用docker构建一个rabbitMQ集群之类的,将其打包成一个镜像.或者你直接拉取别人配置好的镜像,可以直接在 本机0配置的运行一个安装有mq集群容器(类似虚拟机).

  • Jenkins:可以从git或github等代码源,自动拉取代码,进行你想要的测试/构建操作,或shell脚本等.

  • 简单介绍下我要实现的效果:

  1. 在docker容器中运行jenkins.

  2. jenkins自动监听我的Github项目,当我提交新的代码时,

  3. jenkins自动拉取该代码到本地,使用maven打包后,关闭上个版本的该项目,然后使用java jar命令运行最新的jar(spring boot).

Docker安装及基本操作

  • Docker (CE)小企业或个人,此处是CE
  • Docker (EE)企业级
  • 安装及启动
CentOS
前置环境安装
    yum install -y yum-utils 
      device-mapper-persistent-data 
      lvm2

设置稳定的存储库
    yum-config-manager 
        --add-repo 
        https://download.docker.com/linux/centos/docker-ce.repo
    非必须 开启edge和test存储库库等  这样可以禁用yum-config-manager--disable docker-ce-edge
    yum-config-manager --enable docker-ce-edge 
    yum-config-manager --enable docker-ce-test

安装
    yum install docker-ce

启动docker(配置文件 /etc/sysconfig/docker)
    systemctl start docker

开机启动等
    systemctl enable docker
    systemctl disable docker
    chkconfig docker on

测试(这个命令下载一个helloword镜像并在容器中运行。容器运行时,会打印一条信息消息。)
    docker run hello-world

卸载
    yum remove docker-ce
    rm -rf /var/lib/docker # 删除镜像容器等
  • 常用命令(使用容器或镜像id时,无需全部id,只需前几个字符即可,也可使用 容器镜像名:版本号 操作)
重要:xx表示不同的命令如,pull、run等。可以查看该命令的帮助,所有参数
docker xx --help

查看本机的镜像,可通过该方法查看到镜像id等信息.
docker images 

获取镜像 name:镜像名  [:tag]:版本,默认为最新的(也就是会自己加上一个参数:latest)
docker pull [options] name[:tag]

删除镜像,需要删除其下所有容器
docker rmi <镜像id>

运行镜像,构建出一个容器. -d表示后台运行. 
docker run -d image 
-p 8080:80  进行端口映射,将nginx的80端口映射到主机的8080端口上,也就是别人访问8080,可以访问到自己的80

查看目前正在运行的容器
docker ps

查看所有容器
docker ps -a

停止容器
docker stop <容器id>

删除容器
docker rm <容器id>

启动一个运行(run)过的容器
docker start <容器id>

在运行的容器中执行命令 
docker exec [options] container command [arg...]
例如:   
docker exec -it <容器id> bash
可以进入一个容器,和虚拟机中一样。也就是容器内部

挂载目录:将宿主机的文件共享给容器
docker run -d --name=test -v /opt/test:/usr/databases docker-test 
test是容器的名字,需唯一;
-v表示创建一个数据卷并挂载到容器里,
示例表示把宿主机的/opt/test目录挂载到容器的/usr/databases目录下;
docker-test是镜像的名字

查看容器当前信息,可在该命令的 Mounts信息中,找到挂载目录信息
docker inspect <容器id>



制作镜像
以下就是 打包镜像tomcat和jpress.war
在某个目录创建文件 Dockerfile 编辑输入如下内容:
    # (继承自哪个镜像)注意,注释不能添加在和命令同行,会报错
    from images(镜像名)   
    # (维护人员信息)
    MAINTAINER ZX [email protected]  
    # (同一目录下要打包成镜像的文件,拷贝到tomcat的运行目录下)
    COPY jpress.war  /usr/local/tomcat/webapps  

此外,有更多命令
RUN : 执行容器中操作系统的命令(例如是CentOS,可以执行 cp mv 等等.)  
EXPOSE : 指定该容器暴露的端口,可以通过多个EXPOSE暴露多个端口
ENV : 设置环境变量 例如 ENV JAVA_HOME /var/java
ADD : 将本地文件拷贝到容器中.例如 /var/xxx.tar.gz  /var 
CMD : 容器启动时执行的命令,最多一条. 
其他命令自行搜索.

然后在目录下使用  
即可创建镜像,注意, . 是当前目录的意思
docker build .   
下面这句 -t是创建镜像并命名, :laster是版本号 ,因为上面的镜像没有命名
docker build  . -t jpress:latest

运行容器
-d表示后台运行 -p表示设置端口映射, jpress是镜像名
docker run -d -p  8888:8080 jpress

运行mysql镜像
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=xxx  images(镜像名)
  • 使用阿里云的Docker镜像加速器
管理控制台 -> 容器镜像服务 -> 镜像加速器 -> 获取到其分配的加速地址
修改 /etc/docker/daemon.json 文件,增加如下,没有时创建
  {
    "registry-mirrors": ["加速地址"]
  }
  • 可在阿里云免费开通 容器镜像服务,创建仓库,将镜像推送过去

Jenkins 持续集成

  • 前置

    • JDK8
    • Docker
  • 使用docker下载镜像jenkinsci/blueocean,并启动运行Jenkins容器

docker run 
      -u root 
      -d 
      -p 8080:8080 
      -v jenkins-data:/var/jenkins_home 
      -v /var/run/docker.sock:/var/run/docker.sock 
      jenkinsci/blueocean

官方文档中还有一个 --rm . 但是提示 -d 和 --rm 相互冲突
其容器中的/var/jenkins_home 被挂载到了我本地的 /var/lib/docker/volumes/jenkins-data/_data 目录下
  • 进入Jenkins容器执行命令
docker exec -it <容器id> bash
  • 查看容器输出的日志
docker logs <容器id> [-f(滚动的)]
  • 或者自行下载war包运行
java -jar jenkins.war --httpPort=8080
  • 可以创建一个基于Jenkins镜像的整合maven的新镜像(jdk在
    jenkinsci/blueocean中已经有一个oepn_jdk(目前没发现有什么问题))
下载maven 
wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz

Dockerfile文件内容
     #继承自jenkins镜像
     FROM jenkinsci/blueocean:latest  
     #维护人员信息
     MAINTAINER ZX [email protected]  
     # 拷贝本地的maven解压包到镜像的/usr下
     ADD apache-maven-3.5.2-bin.tar.gz /var
     # 下载并解压jdk和maven,使用RUN表示执行当前系统(CentOS)命令
     RUN cd /var 
     tar -zxvf  apache-maven-3.5.2-bin.tar.gz 
     # 设置环境变量
     ENV MAVEN_HOME /var/apache-maven-3.5.2
     ENV PATH $MAVEN_HOME/bin:$PATH
     # 开放的端口
     EXPOSE 8080 
     EXPOSE 8081
     EXPOSE 8082 
     EXPOSE 8083 
     EXPOSE 8084 
     EXPOSE 8085 

构建镜像     
docker build . -t <仓库/镜像名:tag>
例如
docker build . -t zzzxxx/jenkins-maven-jdk:1.0

运行该镜像,端口映射可自行调整.
docker run 
          -u root 
          -d 
          -p 8080:8080 
          -p 8081:8081 
          -p 8082:8082 
          -p 8083:8083 
          -p 8084:8084 
          -p 8085:8085 
          -v jenkins-data:/var/jenkins_home 
          -v /var/run/docker.sock:/var/run/docker.sock 
          zzzxxx/jenkins-maven-jdk:1.0

bug:在RUN的最后一句contOS命令后多加了个 ,导致后面的ENV没有有效指定.

为防止maven构建过慢,可自行在/var/maven/conf/setting.xml中增加阿里云镜像
        <mirror>
          <id>nexus-aliyun</id>
          <mirrorOf>*</mirrorOf>
          <name>Nexus aliyun</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public</url>
        </mirror>
  • 启动时会在日志中输出密码,第一次配置时需要用到,也可以根据它的提示,去容器中的该目录找到

  • 启动完成后,访问对应的8080端口,即可.再完成它的一系列简单的配置(安装他推荐的所有插件)

  • 在 系统管理 -> 全局工具配置 中 设置maven和jdk目录等(可按它的选择自动下载maven) 默认集成的jdk等可用echo $JAVA_HOME 输出对应路径

未分类

  • 它的运行后所有数据默认保存的主目录的.jenkins中(例如 /root/.jenkins).可通过在启动前指定 JENKINS_HOME环境变量修改
vim /ect/profile
export JENKINS_HOME=xx
source /etc/profile
  • 安装插件 Maven Integration plugin 和 Github Plugin(默认已经安装)

  • 系统管理–>插件管理–>可选插件->搜索.安装即可 (如果搜不到,可查看已安装插件中是否已经安装.) 如果插件安装失败,可在已安装插件中卸载后,重试. 卸载插件执行后,需要重启jenkins

  • 在docker容器中jenkins的主目录在/var/jenkins_home文件夹中(可用echo $JENKINS_HOME查看).

  • 其中pull下来的项目在./workspace中的对应任务名的文件中

  • jenkins + github配置,实现jenkins能在push到github后,自动pull代码进行打包测试运行

    • github个人页面 -> setting -> developer setting -> personal access tokens -> Generate new token

未分类

未分类

未分类

该token就是OAuth2协议中的access_token.第三方应用(jenkinds)可通过该令牌获取你允许它进行的一些权限.
选择创建令牌.并勾选 repo 和 admin:repo_hook权限.并自行保存好生成的token.
这两个权限主要就是访问你的仓库,并设置你的仓库的监听器(钩子,可以理解为监听器.监听你的push等)

  • 在github上选择你要部署的项目 -> setting -> webHooks -> add webHooks

未分类

未分类

这个钩子,可以配置你要监听的事件.当该事件发生后,会请求你配置的那个url.
此处在Payload URL处填写
http://<你的jenkins的ip>:<端口号>:/github-webhook/
然后使用默认的Just the push event.监听push事件即可.
点击增加

  • jenkins页面: 系统管理->系统设置->GitHub->add Github Server

未分类

未分类

api url中输入 https://api.github.com
然后选择增加 Credentials. 选择类型为Secret text.在secret中输入之前的token即可.
然后测试连接.当返回
Credentials verified for user BrightStarry, rate limit: 4997
表示成功

  • jenkins: 创建一个新的maven项目任务

未分类

未分类

未分类

未分类

未分类

未分类

输入完任务名后,点击确认.
在配置页面,选择github Project.输入你要持续集成的项目的github url

在源码管理处,选择git.输入github项目的git地址.
然后点击 add Credentials,创建一个帐号.需要输入你的github帐号密码.然后在下拉款选择该帐号.
在下方还可选择要监听的分支

在触发器处,选择监听github的刚才配置的github hook.

在Build处指定pom.xml路径(通常就在根目录)和要执行的maven命令.

  • 最后需要在Post Setps中设置执行shell,我尝试很多次,都无法直接使用java -jar命令运行jar.(会将末尾的&省略,无法后台启动).
    • 可以在shell窗口中,增加如下(BUILD_ID=dontKillMe 是防止jenkins杀死我们的后台进程)

未分类

BUILD_ID=dontKillMe
java -jar $JENKINS_HOME/workspace/maven测试/target/zx-test.jar &
  • 或者使用sh脚本执行(这样可以不用手动停止上一个版本的正在运行的jar)
    • 写在jenkins要执行的shell窗口中的脚本 先停止前一个版本的jar.然后再用最新的jar替换掉之前的jar. 然后运行最新的jar
#!/bin/bash 
cd /var
sh stop.sh
cp $JENKINS_HOME/workspace/任务名/target/生成的jar名.jar /var
echo "开始启动"
BUILD_ID=dontKillMe 
java -jar /var/zx-test.jar &
  • stop.sh 停止前一个版本的jar(pid=ps -ef xxx这句的意思是,通过若干过滤找到对应jar的pid记录.$1表示输出后的记录的第一个参数) 在/var中创建该脚本
echo "正在停止之前的jar"
pid=`ps -ef | grep zx-test.jar | grep -v grep | awk '{print $1}'`
if [ -n "$pid" ]
then
echo "kill -9 的pid:" $pid
kill -9 $pid
fi
  • 至此,已经实现,指定github中的某个项目,一旦push上去,jenkins自动pull,执行maven命令构建,并执行shell运行jar.

  • 实现完成后,我才突然发现.如果要让项目自动运行在单独的docker容器中,最好不要让jenkins运行在docker容器中. 直接使用本地的jenkins,打包后,将jar单独封装成一个镜像,或者挂载jar目录到一个容器中.运行该容器即可.

docker-compose中启动镜像失败的问题

正常的docker run启动

java:8u111-jdk是java官方镜像,如下命令可以成功启动一个该镜像的容器:

docker run --name test001 -idt java:8u111-jdk

以上命令创建的容器,可用docker exec -it test001 /bin/bash进入容器,执行我们所需的操作;

docker-compose启动失败

这里写个最简单的docker-compose.yml,然后用docker-compse,内容如下:

master:
  image: java:8u111-jdk

在此文件所在目录下执行docker-compose up -d启动容器,再执行docker ps -a查看容器状态,信息如下所示:

root@rabbitmq:/usr/local/work/test# docker-compose up -d
Creating test_master_1 ... done
root@rabbitmq:/usr/local/work/test# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
bb433fe9984d        java:8u111-jdk      "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       test_master_1

信息显示我们启动的容器状态为Exited (0) About a minute ago,也就是说虽然创建了容器,但是该容器并未正常运行;

控制终端缺失

启动失败是因为缺失了控制终端的配置,这里有两种方式修复;

使用tty参数(推荐使用)

修改docker-compose.yml,增加一个配置tty:true,如下:

master:
  image: java:8u111-jdk
  tty: true

先执行docker-compose down将之前的容器删除,再执行docker-compose up -d启动,可以发现启动成功,并且可以成功进入容器进行操作:

root@rabbitmq:/usr/local/work/test# docker-compose up -d
Creating test_master_1 ... done
root@rabbitmq:/usr/local/work/test# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
f51debaa26ec        java:8u111-jdk      "/bin/bash"         2 seconds ago       Up 2 seconds                            test_master_1
root@rabbitmq:/usr/local/work/test# docker exec -it test_master_1 /bin/bash
root@f51debaa26ec:/# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)

使用exec重新创建容器(不推荐)

这种方式并不推荐,因为这样做虽然可以启动容器,但是只能重新创建一个容器,具体方法如下:

  1. 使用docker-compose up -d命令启动后,由于没有tty:true的配置,容器就退出了;

  2. 这时候执行命令docker-compose run master /bin/bash,会创建一个容器,并且进入这个容器;

  3. 在当前电脑再打开一个控制台,执行docker ps命令,发现新建了一个容器,状态正常;

ubuntu16.04安装最新版docker、docker-compose、docker-machine

安装前说明:

本文将介绍在ubuntu16.04系统下安装和升级docker、docker-compose、docker-machine。

docker:有两个版本:docker-ce(社区版)和docker-ee(企业版)。

    笔者这里介绍安装或升级的是最新版docker-ce(社区版)。

    参考官网地址:https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#os-requirements

docker-compse:可运行和管理多个docker容器。

docker-machine:docker官方提供的docker管理工具。可管理多个docker主机,可搭建swarm集群。

一、docker安装

1、卸载旧版本docker

全新安装时,无需执行该步骤

$ sudo apt-get remove docker docker-engine docker.io

2、更新系统软件

$ sudo apt-get update

3、安装依赖包

$ sudo apt-get install 
    apt-transport-https 
    ca-certificates 
    curl 
    software-properties-common

4、添加官方密钥

执行该命令时,如遇到长时间没有响应说明网络连接不到docker网站,需要使用代-理进行。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

显示OK,表示添加成功.

5、添加仓库

$ sudo add-apt-repository 
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu 
   $(lsb_release -cs) 
   stable"

6、再次更新软件

经实践,这一步不能够省略,我们需要再次把软件更新到最新,否则下一步有可能会报错。

$ sudo apt-get update

7、安装docker

如果想指定安装某一版本,可使用 sudo apt-get install docker-ce= 命令,把替换为具体版本即可。

以下命令没有指定版本,默认就会安装最新版

$ sudo apt-get install docker-ce

8、查看docker版本

$ docker -v

显示“Docker version 17.09.0-ce, build afdb6d4”字样,表示安装成功。

二、docker-compose安装

1、下载docker-compose

$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

2、授权

$ sudo chmod +x /usr/local/bin/docker-compose

3、查看版本信息

$ docker-compose --version

显示出版本信息,即安装成功。

三、docker-machine安装

说明:docker-machine的使用是要基于virtualBox的。如果没有安装安装过,请先安装virtualBox。

1、安装virtualBox

登录virtualBox官网:https://www.virtualbox.org/wiki/Linux_Downloads

找到”Ubuntu 16.04 (“Xenial”) i386 | AMD64″字样,点击“AMD64”进行下载。

下载后,执行以下命令进行安装:

$ sudo dpkg -i virtualbox-5.2_5.2.0-118431_Ubuntu_xenial_amd64.deb

2、下载并安装docker-machine

$ curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine &&
chmod +x /tmp/docker-machine &&
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine

3、查看版本信息

$ docker-machine version

显示出版本信息,即安装成功。

CICD之logstash服务的Dockerfile使用Gitlab Runner打docker包

gitlab提交代码后,经gitlab Runner打docker包,推送到docker仓库,然后kubernetes选择版本更新

Dockerfile

FROM openjdk:8-jre-alpine

# ensure logstash user exists
RUN addgroup -S logstash && adduser -S -G logstash logstash

# install plugin dependencies
RUN apk add --no-cache 
# env: can't execute 'bash': No such file or directory
        bash 
        libc6-compat 
        libzmq

# grab su-exec for easy step-down from root
RUN apk add --no-cache 'su-exec>=0.2'

# https://www.elastic.co/guide/en/logstash/5.0/installing-logstash.html#_apt
# https://artifacts.elastic.co/GPG-KEY-elasticsearch
ENV LOGSTASH_PATH /usr/share/logstash/bin
ENV PATH $LOGSTASH_PATH:$PATH

# LOGSTASH_TARBALL="https://artifacts.elastic.co/downloads/logstash/logstash-5.5.0.tar.gz"

COPY logstash-5.5.0.tar.gz /logstash.tar.gz
RUN set -ex; 
    apk add --no-cache --virtual .fetch-deps 
        ca-certificates 
        gnupg 
        openssl 
        tar ; 
    dir="$(dirname "$LOGSTASH_PATH")"; 
    mkdir -p "$dir"; 
    tar -xf /logstash.tar.gz --strip-components=1 -C "$dir"; 
    rm logstash.tar.gz; 
    apk del .fetch-deps; 
    export LS_SETTINGS_DIR="$dir/config"; 
# if the "log4j2.properties" file exists (logstash 5.x), let's empty it out so we get the default: "logging only errors to the console"
    if [ -f "$LS_SETTINGS_DIR/log4j2.properties" ]; then 
        cp "$LS_SETTINGS_DIR/log4j2.properties" "$LS_SETTINGS_DIR/log4j2.properties.dist"; 
        truncate -s 0 "$LS_SETTINGS_DIR/log4j2.properties"; 
    fi; 
# set up some file permissions
    for userDir in 
        "$dir/config" 
        "$dir/data" 
    ; do 
        if [ -d "$userDir" ]; then 
            chown -R logstash:logstash "$userDir"; 
        fi; 
    done; 
    logstash --version

COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
COPY logstash-shipper.conf /
RUN mkdir -p /data/logs/sincedb
RUN chown logstash.logstash -R /data/logs/sincedb
WORKDIR /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["-f", "/logstash-shipper.conf"]

docker-entrypoint.sh

#!/bin/bash
set -e
mkdir -p /data/logs/sincedb
chown logstash.logstash -R /data/logs/sincedb

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
    set -- logstash "$@"
fi

# Run as user "logstash" if the command is "logstash"
# allow the container to be started with `--user`
if [ "$1" = 'logstash' -a "$(id -u)" = '0' ]; then
    set -- su-exec logstash "$@"
fi

exec "$@"

logstash-5.5.0.tar.gz 从官方下载 https://www.elastic.co/cn/downloads/logstash

logstash-shipper.conf样例

input {
    file {
        path => [ "/data/logs/service/*/*.log"]
        type => "service"
        sincedb_path => "/data/logs/sincedb/service"
        codec => multiline {
            pattern => "^dddd-dd-dd dd:dd:dd.ddd .+"
            negate => true
            what => "previous"
            max_lines => 30
        }       
    }
    file {
        path => [ "/data/logs/web/*/access_log*.log"]
            codec => plain { format => "%{message}" }
        type => "web"
        sincedb_path => "/data/logs/sincedb/web"
    }
}
output {
    if [type] == 'service' {
        kafka {
            codec => plain { format => "%{message}" }
            bootstrap_servers => "139.219.*.*:9092"
        topic_id => "service"
        }
    }
    if [type] == 'web' {
        kafka {
                codec => plain { format => "%{message}" }
            bootstrap_servers => "139.219.*.*:9092"
        topic_id => "web"
        }
    }
}

service的日志开头是2017-12-01 12:01:01,所以pattern匹配时间,根据时间判断日志的起始点;web日志原封不动传过去,output到kafka集群,logstash-indexer从kafka获取日志后归入elasticsearch

logstash-indexer.conf示例

input {
        kafka {
                bootstrap_servers => "139.219.*.*:9092"
                topics => "service"
                type => "service"
        }
        kafka {
                bootstrap_servers =>"139.219.*.*:9092"
                topics => "web"
                type => "web"
        }
}
filter {
    if [type] != ['web'] {
        if "_grokparsefailure" in [tags] {
              drop { }
          }
        grok {
            match => {
                "message" => "%{TIMESTAMP_ISO8601:timestamp} %{GREEDYDATA}"
            }
        }
        date {
            match => ["timestamp","yyyy-MM-dd HH:mm:ss.SSS"]
            locale => "cn"
        }
    }
    if [type] == 'web' {
        if "_grokparsefailure" in [tags] {
              drop { }
          }
        grok {
                match => {
                    "message" => '%{IP} - - [%{HTTPDATE:time}] "%{WORD:methord} %{URIPATHPARAM:request} HTTP/%
{NUMBER:httpversion}" %{NUMBER:response} %{GREEDYDATA}'
                    }
            }
        date {
            match => ["time","dd/MMM/yyyy:HH:mm:ss +d+"]
            locale => "cn"
        }
    }
}
output {
        if [type] == 'service' {
                elasticsearch {
                        hosts => "172.16.1.1:9200"
                        index => "bbotte-service-%{+YYYY.MM.dd}"
                }
        }
        if [type] == 'web' {
                elasticsearch {
                        hosts => "172.16.1.1:9200"
                        index => "bbotte-web-%{+YYYY.MM.dd}"
                }
        }
}

最后就是gitlabci配置示例

# cat .gitlab-ci.yml
image: docker:latest

stages:
  - LogstashPubTest
  - LogstashPubProd

image-build-test:
  stage: LogstashPubTest
  script:
    - "current_date=`TZ='UTC-8' date +'%m%d%H%M'`"
    - "commit_sha=$CI_COMMIT_SHA"
    - "docker build -t bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8} ."
    - "docker login -u admin -p 123456 bbotte.com:5000"
    - "docker push bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8}"
  only:
    - test
image-build-master:
  stage: LogstashPubProd
  script:
    - "current_date=`TZ='UTC-8' date +'%m%d%H%M'`"
    - "commit_sha=$CI_COMMIT_SHA"
    - "docker build -t bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8} ."
    - "docker login -u admin -p 123456 bbotte.com:5000"
    - "docker push bbotte.com:5000/logstash:$CI_COMMIT_REF_NAME-$current_date-${commit_sha:0:8}"
  only:
    - master

目录结构如下:

logstash$ ls -a
.   docker-entrypoint.sh  .git            logstash-5.5.0.tar.gz 
..  Dockerfile            .gitlab-ci.yml  logstash-shipper.conf

使用Docker部署一个tomcat+mysql应用玩玩

Docker已经成为目前最流行的系统部署解决方案,几乎可以部署任何系统应用,下面我们用两个小实例,来熟悉一下Docker.

前提是要先安装好一个docker环境,可以参考:

http://www.lazyedu.com/docker/centos-docker.html

一、使用docker部署debain镜像

1、获取最新的debain镜像

docker pull debain    

这一步docker会从docker.io官方服务资源中下载最新的debain系统镜像

2、成功后查看已经下载images

docker images 

3、运行并进入docker容器

docker run -t -i debian /bin/bash

exit命令可以退出容器。

4、进入一个已经启动了的容器

docker exec -it debian /bin/bash

5、查看已经运行的容器

docker ps

6、查看全部容器,包括没有启动的

docker ps -a

简单的几个命令,我们就可以开始Docker的学习了。

二、使用docker部署mysql

1、下载mysql官方镜像,同样系统会自动下载最新版本的mysql

docker pull mysql

2、运行方式A

运行docker容器,使用外部配置文件、日志、数据库,设置初始密码

docker run -p 3306:3306 --name mysql -v $PWD/conf/:/etc/mysql/ -v $PWD/logs:/var/log/mysql -v $PWD/data:/usr/share/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql

$PWD表示你在运行docker中的mysql数据库时的系统目录,如果你希望docker使用你本地磁盘中的配置文件、系统日志和数据库文件时,可以使用上面的参数,不过这时需要你在当前目录中有这些文件。记住设个-v参数,如果有需要让docker运行时访问你本地硬盘时都可以使用这个参数创建容器。

3、运行方式B

运行docker容器,使用内部文件,设置初始密码:

docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql

要注意的是,不管用什么方式创建和启动mysql docker容器,都需要制定初始的root密码,通过-e MYSQL_ROOT_PASSWORD=123456参数制定,不然这个容器是不会启动的。

三、使用docker部署tomcat

1、下载官方最新版本

docker pull tomcat

2、运行方式

这种方式创建容器,是将你当前目录中test应用映射到docker中的ROOT

docker run --name tomcat -p 8080:8080 -v $PWD/test:/usr/local/tomcat/webapps/ROOT -d tomcat  

这种方式只在docker中启动tomcat

docker run --name tomcat -p 8080:8080 -d tomcat  

我们可以使用下面的命令进入这个tomcat容器

docker exec -it tomcat bash

经过这个几个实例,我们来体验一下什么docker,它是如何运行的部署的,我们后面会逐渐深入解析docker。

如何清理Docker占用的磁盘空间?

摘要:用了Docker,好处挺多的,但是有一个不大不小的问题,它会一不小心占用太多磁盘,这就意味着我们必须及时清理。

未分类

作为一个有信仰的技术公司,我们Fundebug的后台采用了酷炫的全Docker化架构,所有服务,包括数据库都运行在Docker里面。这样做当然不是为了炫技,看得清楚的好处还是不少的:

  • 所有服务器的配置都非常简单,只安装了Docker,这样新增服务器的时候要简单很多。

  • 可以非常方便地在服务器之间移动各种服务,下载Docker镜像就可以运行,不需要手动配置运行环境。

  • 开发/测试环境与生产环境严格一致,不用担心由于环境问题导致部署失败。

至少,上线这一年多来,Docker一直非常稳定,没有出什么问题。但是,它有一个不大不小的问题,会比较消耗磁盘空间。

如果Docker一不小心把磁盘空间全占满了,你的服务也就算玩完了,因此所有Docker用户都需要对此保持警惕。当然,大家也不要紧张,这个问题还是挺好解决的。

1. docker system命令

在谁用光了磁盘?Docker System命令详解中,我们详细介绍了docker system命令,它可以用于管理磁盘空间。

docker system df命令,类似于Linux上的df命令,用于查看Docker的磁盘使用情况:

docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              147                 36                  7.204GB             3.887GB (53%)
Containers          37                  10                  104.8MB             102.6MB (97%)
Local Volumes       3                   3                   1.421GB             0B (0%)
Build Cache                                                 0B                  0B

可知,Docker镜像占用了7.2GB磁盘,Docker容器占用了104.8MB磁盘,Docker数据卷占用了1.4GB磁盘。

docker system prune命令可以用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及dangling镜像(即无tag的镜像)。docker system prune -a命令清理得更加彻底,可以将没有容器使用Docker镜像都删掉。注意,这两个命令会把你暂时关闭的容器,以及暂时没有用到的Docker镜像都删掉了…所以使用之前一定要想清楚吶。

执行docker system prune -a命令之后,Docker占用的磁盘空间减少了很多:

docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              10                  10                  2.271GB             630.7MB (27%)
Containers          10                  10                  2.211MB             0B (0%)
Local Volumes       3                   3                   1.421GB             0B (0%)
Build Cache                                                 0B                  0B

2. 手动清理Docker镜像/容器/数据卷

对于旧版的Docker(版本1.13之前),是没有docker system命令的,因此需要进行手动清理。这里给出几个常用的命
删除所有关闭的容器

docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm

删除所有dangling镜像(即无tag的镜像):

docker rmi $(docker images | grep "^<none>" | awk "{print $3}")

删除所有dangling数据卷(即无用的volume):

docker volume rm $(docker volume ls -qf dangling=true)

Fundebug提供实时、专业的错误监控服务,为您的线上代码保驾护航,欢迎大家免费使用!

3. 限制容器的日志大小

有一次,当我使用1与2提到的方法清理磁盘之后,发现并没有什么作用,于是,我进行了一系列分析。

在Ubuntu上,Docker的所有相关文件,包括镜像、容器等都保存在/var/lib/docker/目录中:

du -hs /var/lib/docker/
97G /var/lib/docker/

Docker竟然使用了将近100GB磁盘,这也是够了。使用du命令继续查看,可以定位到真正占用这么多磁盘的目录:

92G /var/lib/docker/containers/a376aa694b22ee497f6fc9f7d15d943de91c853284f8f105ff5ad6c7ddae7a53

由docker ps可知,nginx容器的ID恰好为a376aa694b22,与上面的目录/var/lib/docker/containers/a376aa694b22的前缀一致:

docker ps
CONTAINER ID        IMAGE                                       COMMAND                  CREATED             STATUS              PORTS               NAMES
a376aa694b22        192.168.59.224:5000/nginx:1.12.1            "nginx -g 'daemon off"   9 weeks ago         Up 10 minutes                           nginx

因此,nginx容器竟然占用了92GB的磁盘。进一步分析可知,真正占用磁盘空间的是nginx的日志文件。那么这就不难理解了。我们Fundebug每天的数据请求为百万级别,那么日志数据自然非常大。

使用truncate命令,可以将nginx容器的日志文件“清零”:

truncate -s 0 /var/lib/docker/containers/a376aa694b22ee497f6fc9f7d15d943de91c853284f8f105ff5ad6c7ddae7a53/*-json.log

当然,这个命令只是临时有作用,日志文件迟早又会涨回来。要从根本上解决问题,需要限制nginx容器的日志文件大小。这个可以通过配置日志的max-size来实现,下面是nginx容器的docker-compose配置文件:

nginx:
  image: nginx:1.12.1
  restart: always
  logging:
    driver: "json-file"
    options:
      max-size: "5g"

重启nginx容器之后,其日志文件的大小就被限制在5GB,再也不用担心了~

4. 重启Docker

还有一次,当我清理了镜像、容器以及数据卷之后,发现磁盘空间并没有减少。根据Docker disk usage提到过的建议,我重启了Docker,发现磁盘使用率从83%降到了19%。根据高手指点,这应该是与内核3.13相关的BUG,导致Docker无法清理一些无用目录:

it’s quite likely that for some reason when those container shutdown, docker couldn’t remove the directory because the shm device was busy. This tends to happen often on 3.13 kernel. You may want to update it to the 4.4 version supported on trusty 14.04.5 LTS.

The reason it disappeared after a restart, is that daemon probably tried and succeeded to clean up left over data from stopped containers.

我查看了一下内核版本,发现真的是3.13:

uname -r
3.13.0-86-generic

如果你的内核版本也是3.13,而且清理磁盘没能成功,不妨重启一下Docker。当然,这个晚上操作比较靠谱。

Docker 搭建 etcd 集群

etcd 是 CoreOS 团队发起的一个开源项目(Go 语言,其实很多这类项目都是 Go 语言实现的,只能说很强大),实现了分布式键值存储和服务发现,etcd 和 ZooKeeper/Consul 非常相似,都提供了类似的功能,以及 REST API 的访问操作,具有以下特点:

  • 简单:安装和使用简单,提供了 REST API 进行操作交互

  • 安全:支持 HTTPS SSL 证书

  • 快速:支持并发 10 k/s 的读写操作

  • 可靠:采用 raft 算法,实现分布式系统数据的可用性和一致性

etcd 可以单个实例使用,也可以进行集群配置,因为很多项目都是以 etcd 作为服务发现,比如 CoreOS 和 Kubernetes,所以,下面我们使用 Docker 简单搭建一下 etcd 集群。

未分类

1. 主机安装

如果不使用 Docker 的话,etcd 在主机上安装,也非常简单。

Linux 安装命令:

$ curl -L  https://github.com/coreos/etcd/releases/download/v3.3.0-rc.0/etcd-v3.3.0-rc.0-linux-amd64.tar.gz -o etcd-v3.3.0-rc.0-linux-amd64.tar.gz && 
sudo tar xzvf etcd-v3.3.0-rc.0-linux-amd64.tar.gz && 
cd etcd-v3.3.0-rc.0-linux-amd64 && 
sudo cp etcd* /usr/local/bin/

其实就是将编译后的二进制文件,拷贝到/usr/local/bin/目录,各个版本的二进制文件,可以从 https://github.com/coreos/etcd/releases/ 中查找下载。

Mac OS 安装命令:

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
$ brew install etcd

执行下面命令,查看 etcd 是否安装成功:

$ etcd --version
etcd Version: 3.2.12
Git SHA: GitNotFound
Go Version: go1.9.2
Go OS/Arch: darwin/amd64

2. 集群搭建

搭建 etcd 集群,需要借助下 Docker Machine 创建三个 Docker 主机,命令:

$ docker-machine create -d virtualbox manager1 && 
docker-machine create -d virtualbox worker1 && 
docker-machine create -d virtualbox worker2

$ docker-machine ls
NAME       ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
manager1   -        virtualbox   Running   tcp://192.168.99.100:2376           v17.11.0-ce   
worker1    -        virtualbox   Running   tcp://192.168.99.101:2376           v17.11.0-ce   
worker2    -        virtualbox   Running   tcp://192.168.99.102:2376           v17.11.0-ce   

为防止 Docker 主机中垃取官方镜像,速度慢的问题,我们还需要将 etcd 镜像打包推送到私有仓库中,命令:

$ docker tag quay.io/coreos/etcd 192.168.99.1:5000/quay.io/coreos/etcd:latest && 
docker push 192.168.99.1:5000/quay.io/coreos/etcd:latest && 
docker pull 192.168.99.1:5000/quay.io/coreos/etcd:latest

另外,还需要将私有仓库地址配置在 Docker 主机中,并重启三个 Docker 主机,具体配置参考:Docker 三剑客之 Docker Swarm

Docker 主机配置好之后,我们需要使用docker-machine ssh命令,分别进入三个 Docker 主机中,执行 Docker etcd 配置命令。

manager1 主机(node1 192.168.99.100):

$ docker run -d --name etcd 
    -p 2379:2379 
    -p 2380:2380 
    --volume=etcd-data:/etcd-data 
    192.168.99.1:5000/quay.io/coreos/etcd 
    /usr/local/bin/etcd 
    --data-dir=/etcd-data --name node1 
    --initial-advertise-peer-urls http://192.168.99.100:2380 --listen-peer-urls http://0.0.0.0:2380 
    --advertise-client-urls http://192.168.99.100:2379 --listen-client-urls http://0.0.0.0:2379 
    --initial-cluster-state new 
    --initial-cluster-token docker-etcd 
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

worker1 主机(node2 192.168.99.101):

$ docker run -d --name etcd 
    -p 2379:2379 
    -p 2380:2380 
    --volume=etcd-data:/etcd-data 
    192.168.99.1:5000/quay.io/coreos/etcd 
    /usr/local/bin/etcd 
    --data-dir=/etcd-data --name node2 
    --initial-advertise-peer-urls http://192.168.99.101:2380 --listen-peer-urls http://0.0.0.0:2380 
    --advertise-client-urls http://192.168.99.101:2379 --listen-client-urls http://0.0.0.0:2379 
    --initial-cluster-state new 
    --initial-cluster-token docker-etcd 
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

worker2 主机(node1 192.168.99.102):

$ docker run -d --name etcd 
    -p 2379:2379 
    -p 2380:2380 
    --volume=etcd-data:/etcd-data 
    192.168.99.1:5000/quay.io/coreos/etcd 
    /usr/local/bin/etcd 
    --data-dir=/etcd-data --name node3 
    --initial-advertise-peer-urls http://192.168.99.102:2380 --listen-peer-urls http://0.0.0.0:2380 
    --advertise-client-urls http://192.168.99.102:2379 --listen-client-urls http://0.0.0.0:2379 
    --initial-cluster-state existing 
    --initial-cluster-token docker-etcd 
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

先来说明下 etcd 各个配置参数的意思(参考自 etcd 使用入门):

  • –name:节点名称,默认为 default。
  • –data-dir:服务运行数据保存的路径,默认为${name}.etcd。
  • –snapshot-count:指定有多少事务(transaction)被提交时,触发截取快照保存到磁盘。
  • –heartbeat-interval:leader 多久发送一次心跳到 followers。默认值是 100ms。
  • –eletion-timeout:重新投票的超时时间,如果 follow 在该时间间隔没有收到心跳包,会触发重新投票,默认为 1000 ms。
  • –listen-peer-urls:和同伴通信的地址,比如http://ip:2380,如果有多个,使用逗号分隔。需要所有节点都能够访问,所以不要使用 localhost!
  • –listen-client-urls:对外提供服务的地址:比如http://ip:2379,http://127.0.0.1:2379,客户端会连接到这里和 etcd 交互。
  • –advertise-client-urls:对外公告的该节点客户端监听地址,这个值会告诉集群中其他节点。
  • –initial-advertise-peer-urls:该节点同伴监听地址,这个值会告诉集群中其他节点。
  • –initial-cluster:集群中所有节点的信息,格式为node1=http://ip1:2380,node2=http://ip2:2380,…,注意:这里的 node1 是节点的 –name 指定的名字;后面的 ip1:2380 是 –initial-advertise-peer-urls 指定的值。
  • –initial-cluster-state:新建集群的时候,这个值为 new;假如已经存在的集群,这个值为 existing。
  • –initial-cluster-token:创建集群的 token,这个值每个集群保持唯一。这样的话,如果你要重新创建集群,即使配置和之前一样,也会再次生成新的集群和节点 uuid;否则会导致多个集群之间的冲突,造成未知的错误。

上述配置也可以设置配置文件,默认为/etc/etcd/etcd.conf。

我们可以使用docker ps,查看 Docker etcd 是否配置成功:

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                              NAMES
463380d23dfe        192.168.99.1:5000/quay.io/coreos/etcd   "/usr/local/bin/et..."   2 hours ago         Up 2 hours          0.0.0.0:2379-2380->2379-2380/tcp   etcd

然后进入其中一个 Docker 主机:

$ docker exec -it etcd bin/sh

执行下面命令(查看集群成员):

$ etcdctl member list
773d30c9fc6640b4: name=node2 peerURLs=http://192.168.99.101:2380 clientURLs=http://192.168.99.101:2379 isLeader=true
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=false
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

可以看到,集群里面有三个成员,并且node2为管理员,node1和node3为普通成员。

etcdctl 是 ectd 的客户端命令工具(也是 go 语言实现),里面封装了 etcd 的 REST API 执行命令,方便我们进行操作 etcd,后面再列出 etcdctl 的命令详细说明。

上面命令的 etcd API 版本为 2.0,我们可以手动设置版本为 3.0,命令:

$ export ETCDCTL_API=3 && /usr/local/bin/etcdctl put foo bar
OK

部分命令和执行结果还是和 2.0 版本,有很多不同的,比如同是查看集群成员,3.0 版本的执行结果:

$ etcdctl member list
773d30c9fc6640b4, started, node2, http://192.168.99.101:2380, http://192.168.99.101:2379
b2b0bca2e0cfcc19, started, node3, http://192.168.99.102:2380, http://192.168.99.102:2379
c88e2cccbb287a01, started, node1, http://192.168.99.100:2380, http://192.168.99.100:2379

好了,我们现在再演示一种情况,就是从集群中移除一个节点,然后再把它添加到集群中,为演示 etcd 中使用 Raft 算法,我们将node2管理节点,作为操作对象。

我们在随便一个主机 etcd 容器中(node2除外),执行成员移除集群命令(必须使用 ID,使用别名会报错):

$ etcdctl member remove 773d30c9fc6640b4
Member 773d30c9fc6640b4 removed from cluster f84185fa5f91bdf6

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

会发现node2管理节点被移除集群了,并且通过 Raft 算法,node3被推举为管理节点。

在将node2节点重新加入集群之前,我们需要执行下面命令:

$ etcdctl member add node2 --peer-urls="http://192.168.99.101:2380"
Member 22b0de6ffcd98f00 added to cluster f84185fa5f91bdf6

ETCD_NAME="node2"
ETCD_INITIAL_CLUSTER="node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380,node1=http://192.168.99.100:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

可以看到,ETCD_INITIAL_CLUSTER_STATE 值为existing,也就是我们配置的–initial-cluster-state参数。

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
22b0de6ffcd98f00[unstarted]: peerURLs=http://192.168.99.101:2380
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

会发现22b0de6ffcd98f00成员状态变为了unstarted。

我们在node2节点,执行 Docker etcd 集群配置命令:

$ docker run -d --name etcd 
    -p 2379:2379 
    -p 2380:2380 
    --volume=etcd-data:/etcd-data 
    192.168.99.1:5000/quay.io/coreos/etcd 
    /usr/local/bin/etcd 
    --data-dir=/etcd-data --name node2 
    --initial-advertise-peer-urls http://192.168.99.101:2380 --listen-peer-urls http://0.0.0.0:2380 
    --advertise-client-urls http://192.168.99.101:2379 --listen-client-urls http://0.0.0.0:2379 
    --initial-cluster-state existing 
    --initial-cluster-token docker-etcd 
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

结果并不像我们想要的那样成功,执行查看日志:

$ docker logs etcd
2017-12-25 08:19:30.160967 I | etcdmain: etcd Version: 3.2.12
2017-12-25 08:19:30.161062 I | etcdmain: Git SHA: b19dae0
2017-12-25 08:19:30.161082 I | etcdmain: Go Version: go1.8.5
2017-12-25 08:19:30.161092 I | etcdmain: Go OS/Arch: linux/amd64
2017-12-25 08:19:30.161105 I | etcdmain: setting maximum number of CPUs to 1, total number of available CPUs is 1
2017-12-25 08:19:30.161144 N | etcdmain: the server is already initialized as member before, starting as etcd member...
2017-12-25 08:19:30.161195 I | embed: listening for peers on http://0.0.0.0:2380
2017-12-25 08:19:30.161232 I | embed: listening for client requests on 0.0.0.0:2379
2017-12-25 08:19:30.165269 I | etcdserver: name = node2
2017-12-25 08:19:30.165317 I | etcdserver: data dir = /etcd-data
2017-12-25 08:19:30.165335 I | etcdserver: member dir = /etcd-data/member
2017-12-25 08:19:30.165347 I | etcdserver: heartbeat = 100ms
2017-12-25 08:19:30.165358 I | etcdserver: election = 1000ms
2017-12-25 08:19:30.165369 I | etcdserver: snapshot count = 100000
2017-12-25 08:19:30.165385 I | etcdserver: advertise client URLs = http://192.168.99.101:2379
2017-12-25 08:19:30.165593 I | etcdserver: restarting member 773d30c9fc6640b4 in cluster f84185fa5f91bdf6 at commit index 14
2017-12-25 08:19:30.165627 I | raft: 773d30c9fc6640b4 became follower at term 11
2017-12-25 08:19:30.165647 I | raft: newRaft 773d30c9fc6640b4 [peers: [], term: 11, commit: 14, applied: 0, lastindex: 14, lastterm: 11]
2017-12-25 08:19:30.169277 W | auth: simple token is not cryptographically signed
2017-12-25 08:19:30.170424 I | etcdserver: starting server... [version: 3.2.12, cluster version: to_be_decided]
2017-12-25 08:19:30.171732 I | etcdserver/membership: added member 773d30c9fc6640b4 [http://192.168.99.101:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.171845 I | etcdserver/membership: added member c88e2cccbb287a01 [http://192.168.99.100:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.171877 I | rafthttp: starting peer c88e2cccbb287a01...
2017-12-25 08:19:30.171902 I | rafthttp: started HTTP pipelining with peer c88e2cccbb287a01
2017-12-25 08:19:30.175264 I | rafthttp: started peer c88e2cccbb287a01
2017-12-25 08:19:30.175339 I | rafthttp: added peer c88e2cccbb287a01
2017-12-25 08:19:30.178326 I | etcdserver/membership: added member cbd7fa8d01297113 [http://192.168.99.102:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.178383 I | rafthttp: starting peer cbd7fa8d01297113...
2017-12-25 08:19:30.178410 I | rafthttp: started HTTP pipelining with peer cbd7fa8d01297113
2017-12-25 08:19:30.179794 I | rafthttp: started peer cbd7fa8d01297113
2017-12-25 08:19:30.179835 I | rafthttp: added peer cbd7fa8d01297113
2017-12-25 08:19:30.180062 N | etcdserver/membership: set the initial cluster version to 3.0
2017-12-25 08:19:30.180132 I | etcdserver/api: enabled capabilities for version 3.0
2017-12-25 08:19:30.180255 N | etcdserver/membership: updated the cluster version from 3.0 to 3.2
2017-12-25 08:19:30.180430 I | etcdserver/api: enabled capabilities for version 3.2
2017-12-25 08:19:30.183979 I | rafthttp: started streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.184139 I | rafthttp: started streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.184232 I | rafthttp: started streaming with peer c88e2cccbb287a01 (stream MsgApp v2 reader)
2017-12-25 08:19:30.185142 I | rafthttp: started streaming with peer c88e2cccbb287a01 (stream Message reader)
2017-12-25 08:19:30.186518 I | etcdserver/membership: removed member cbd7fa8d01297113 from cluster f84185fa5f91bdf6
2017-12-25 08:19:30.186573 I | rafthttp: stopping peer cbd7fa8d01297113...
2017-12-25 08:19:30.186614 I | rafthttp: started streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186786 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186815 I | rafthttp: started streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186831 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186876 I | rafthttp: started streaming with peer cbd7fa8d01297113 (stream MsgApp v2 reader)
2017-12-25 08:19:30.187224 I | rafthttp: started streaming with peer cbd7fa8d01297113 (stream Message reader)
2017-12-25 08:19:30.187647 I | rafthttp: stopped HTTP pipelining with peer cbd7fa8d01297113
2017-12-25 08:19:30.187682 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (stream MsgApp v2 reader)
2017-12-25 08:19:30.187873 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (stream Message reader)
2017-12-25 08:19:30.187895 I | rafthttp: stopped peer cbd7fa8d01297113
2017-12-25 08:19:30.187911 I | rafthttp: removed peer cbd7fa8d01297113
2017-12-25 08:19:30.188034 I | etcdserver/membership: added member b2b0bca2e0cfcc19 [http://192.168.99.102:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.188059 I | rafthttp: starting peer b2b0bca2e0cfcc19...
2017-12-25 08:19:30.188075 I | rafthttp: started HTTP pipelining with peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188510 I | rafthttp: started peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188533 I | rafthttp: added peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188795 I | etcdserver/membership: removed member 773d30c9fc6640b4 from cluster f84185fa5f91bdf6
2017-12-25 08:19:30.193643 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.193730 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.193797 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (stream MsgApp v2 reader)
2017-12-25 08:19:30.194782 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (stream Message reader)
2017-12-25 08:19:30.195663 I | raft: 773d30c9fc6640b4 [term: 11] received a MsgHeartbeat message with higher term from b2b0bca2e0cfcc19 [term: 12]
2017-12-25 08:19:30.195716 I | raft: 773d30c9fc6640b4 became follower at term 12
2017-12-25 08:19:30.195736 I | raft: raft.node: 773d30c9fc6640b4 elected leader b2b0bca2e0cfcc19 at term 12
2017-12-25 08:19:30.196617 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.197064 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.197846 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.198242 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.201771 E | etcdserver: the member has been permanently removed from the cluster
2017-12-25 08:19:30.202060 I | etcdserver: the data-dir used by this member must be removed.
2017-12-25 08:19:30.202307 E | etcdserver: publish error: etcdserver: request cancelled
2017-12-25 08:19:30.202338 I | etcdserver: aborting publish because server is stopped
2017-12-25 08:19:30.202364 I | rafthttp: stopping peer b2b0bca2e0cfcc19...
2017-12-25 08:19:30.202482 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.202504 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.204143 I | rafthttp: stopped HTTP pipelining with peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.204186 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (stream MsgApp v2 reader)
2017-12-25 08:19:30.204205 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (stream Message reader)
2017-12-25 08:19:30.204217 I | rafthttp: stopped peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.204228 I | rafthttp: stopping peer c88e2cccbb287a01...
2017-12-25 08:19:30.204241 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.204255 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.204824 I | rafthttp: stopped HTTP pipelining with peer c88e2cccbb287a01
2017-12-25 08:19:30.204860 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (stream MsgApp v2 reader)
2017-12-25 08:19:30.204878 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (stream Message reader)
2017-12-25 08:19:30.204891 I | rafthttp: stopped peer c88e2cccbb287a01

这么长的日志,说明啥问题呢,就是说我们虽然重新执行的 etcd 创建命令,但因为读取之前配置文件的关系,etcd 会恢复之前的集群成员,但之前的集群节点已经被移除了,所以集群节点就一直处于停止状态。

怎么解决呢?很简单,就是将我们之前创建的etcd-data数据卷轴删掉,命令:

$ docker volume ls
DRIVER              VOLUME NAME
local               etcd-data

$ docker volume rm etcd-data
etcd-data

然后,再在node2节点,重新执行 Docker etcd 集群配置命令(上面),会发现执行是成功的。

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
22b0de6ffcd98f00: name=node2 peerURLs=http://192.168.99.101:2380 clientURLs=http://192.168.99.101:2379 isLeader=false
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

3. API 操作

etcd REST API 被用于键值操作和集群成员操作,这边就简单说几个,详细的 API 查看附录说明。

3.1 键值管理

设置键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello -XPUT -d value="hello world"
{"action":"set","node":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

查看键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello
{"action":"get","node":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

删除键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello -XDELETE
{"action":"delete","node":{"key":"/hello","modifiedIndex":19,"createdIndex":17},"prevNode":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

3.2 成员管理

列出集群中的所有成员:

$ curl http://127.0.0.1:2379/v2/members
{"members":[{"id":"22b0de6ffcd98f00","name":"node2","peerURLs":["http://192.168.99.101:2380"],"clientURLs":["http://192.168.99.101:2379"]},{"id":"b2b0bca2e0cfcc19","name":"node3","peerURLs":["http://192.168.99.102:2380"],"clientURLs":["http://192.168.99.102:2379"]},{"id":"c88e2cccbb287a01","name":"node1","peerURLs":["http://192.168.99.100:2380"],"clientURLs":["http://192.168.99.100:2379"]}]}

查看当前节点是否为管理节点:

$ curl http://127.0.0.1:2379/v2/stats/leader
{"leader":"b2b0bca2e0cfcc19","followers":{"22b0de6ffcd98f00":{"latency":{"current":0.001051,"average":0.0029195000000000002,"standardDeviation":0.001646769458667484,"minimum":0.001051,"maximum":0.006367},"counts":{"fail":0,"success":10}},"c88e2cccbb287a01":{"latency":{"current":0.000868,"average":0.0022389999999999997,"standardDeviation":0.0011402923601720172,"minimum":0.000868,"maximum":0.004725},"counts":{"fail":0,"success":12}}}}

查看当前节点信息:

$ curl http://127.0.0.1:2379/v2/stats/self
{"name":"node3","id":"b2b0bca2e0cfcc19","state":"StateLeader","startTime":"2017-12-25T06:00:28.803429523Z","leaderInfo":{"leader":"b2b0bca2e0cfcc19","uptime":"36m45.45263851s","startTime":"2017-12-25T08:13:02.103896843Z"},"recvAppendRequestCnt":6,"sendAppendRequestCnt":22}

查看集群状态:

$ curl http://127.0.0.1:2379/v2/stats/store
{"getsSuccess":9,"getsFail":4,"setsSuccess":9,"setsFail":0,"deleteSuccess":3,"deleteFail":0,"updateSuccess":0,"updateFail":0,"createSuccess":7,"createFail":0,"compareAndSwapSuccess":0,"compareAndSwapFail":0,"compareAndDeleteSuccess":0,"compareAndDeleteFail":0,"expireCount":0,"watchers":0}

当然也可以通过 API 添加和删除集群成员。

4. API 说明和 etcdctl 命令说明

etcd REST API 说明(v2 版本):

未分类

更多 API 请查看:https://coreos.com/etcd/docs/latest/v2/api.html 和 https://coreos.com/etcd/docs/latest/v2/members_api.html

etcdctl 命令说明:

未分类

Docker Swarm 下搭建 MongoDB 分片+副本+选举集群

一、环境准备

三台服务器,建立 Docker Swarm 集群,一个 Manager,两个 Worker。

  • docker 版本:17-09
  • mongo 版本:3.6

二、MongoDB 集群架构设计

未分类

高清图地址: https://www.processon.com/view/link/5a3c7386e4b0bf89b8530376

三、搭建集群

1、【Manager】创建集群网络

docker network create -d overlay --attachable mongo

–attachable 允许其他容器加入此网络

2、创建 9 个 Data 服务,3 个 Config 服务,1 个 Global 模式的 Mongos 服务

2.1、【所有机器】创建相关文件夹

mkdir /root/mongo/config /root/mongo/shard1 /root/mongo/shard2 /root/mongo/shard3

2.2、【Manager】创建 stack.yml

version: '3.3'
services:
  mongors1n1:
    # docker 中国的镜像加速地址
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        # 指定在服务器 manager 上启动
        constraints:
          - node.hostname==manager
  mongors2n1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  mongors3n1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  mongors1n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors2n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors3n2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  mongors1n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard1 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard1:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongors2n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard2 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard2:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongors3n3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --shardsvr --replSet shard3 --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/shard3:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  cfg1:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==manager
  cfg2:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker1
  cfg3:
    image: registry.docker-cn.com/library/mongo
    command: mongod --configsvr --replSet cfgrs --smallfiles --dbpath /data/db --port 27017
    networks:
      - mongo
    volumes:
      - /etc/localtime:/etc/localtime
      - /root/mongo/config:/data/db
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
        constraints:
          - node.hostname==worker2
  mongos:
    image: registry.docker-cn.com/library/mongo
    # mongo 3.6 版默认绑定IP为 127.0.0.1,此处绑定 0.0.0.0 是允许其他容器或主机可以访问
    command: mongos --configdb cfgrs/cfg1:27017,cfg2:27017,cfg3:27017 --bind_ip 0.0.0.0 --port 27017
    networks:
      - mongo
    # 映射宿主机的 27017 端口
    ports:
      - 27017:27017
    volumes:
      - /etc/localtime:/etc/localtime
    depends_on:
      - cfg1
      - cfg2
      - cfg3
    deploy:
      restart_policy:
        condition: on-failure
      # 在集群内的每一台服务器上都启动一个容器
      mode: global
networks:
  mongo:
    external: true

2.3、启动服务,在 Manager 上执行

docker stack deploy -c stack.yml mongo

2.4、【Manager】查看服务的启动情况

docker service ls

正常情况下,会出现如下结果:

[docker@manager ~]# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                                         PORTS
z1l5zlghlfbi        mongo_cfg1          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
lg9vbods29th        mongo_cfg2          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
i6d6zwxsq0ss        mongo_cfg3          replicated          1/1                 registry.docker-cn.com/library/mongo:latest
o0lfdavd8kpj        mongo_mongors1n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
n85yeyod7mlu        mongo_mongors1n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
cwurdqng9tdk        mongo_mongors1n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
vu6al5kys28u        mongo_mongors2n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
xrjiep0vrf0w        mongo_mongors2n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
qqzifwcejjyk        mongo_mongors2n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
tddgw8hygv1b        mongo_mongors3n1    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
qrb6fjty03mw        mongo_mongors3n2    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
m8ikdzjssmhn        mongo_mongors3n3    replicated          1/1                 registry.docker-cn.com/library/mongo:latest
mnnlm49b7kyb        mongo_mongos        global              3/3                 registry.docker-cn.com/library/mongo:latest   *:27017->27017/tcp

3、初始化集群

3.1 【Manager】初始化 Mongo 配置集群

docker exec -it $(docker ps | grep "cfg1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id: "cfgrs",configsvr: true, members: [{ _id : 0, host : "cfg1" },{ _id : 1, host : "cfg2" }, { _id : 2, host : "cfg3" }]})' | mongo"

3.2 【Manager】初始化三个 Mongo 数据集群

docker exec -it $(docker ps | grep "mongors1n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard1", members: [{ _id : 0, host : "mongors1n1" },{ _id : 1, host : "mongors1n2" },{ _id : 2, host : "mongors1n3", arbiterOnly: true }]})' | mongo"

docker exec -it $(docker ps | grep "mongors2n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard2", members: [{ _id : 0, host : "mongors2n1" },{ _id : 1, host : "mongors2n2" },{ _id : 2, host : "mongors2n3", arbiterOnly: true }]})' | mongo"

docker exec -it $(docker ps | grep "mongors3n1" | awk '{ print $1 }') bash -c "echo 'rs.initiate({_id : "shard3", members: [{ _id : 0, host : "mongors3n1" },{ _id : 1, host : "mongors3n2" },{ _id : 2, host : "mongors3n3", arbiterOnly: true }]})' | mongo"

3.3 【Manager】将三个数据集群当做分片加入 mongos

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard1/mongors1n1:27017,mongors1n2:27017,mongors1n3:27017")' | mongo "

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard2/mongors2n1:27017,mongors2n3:27017,mongors2n3:27017")' | mongo "

docker exec -it $(docker ps | grep "mongos" | awk '{ print $1 }') bash -c "echo 'sh.addShard("shard3/mongors3n1:27017,mongors3n2:27017,mongors3n3:27017")' | mongo "

4、连接集群

4.1 内部:在 mongo 网络下的容器,通过 mongos:27017 连接

4.2 外部:通过 IP:27017 连接,IP 可以为三台服务的中的一个的 IP