工欲善其事,必先利其器。
最近想在本地搭建Mysql主备、集群环境。之前的做法要么是本地起多个实例,绑定不同的端口;要么是创建多个虚拟机,但虚拟机资源占用高,搭建效率低。目前更轻量快速的方案是使用Docker,在Docker官网上有一个分为6章的《Get started with Docker》文档。本文基于文档整理了环境搭建过程以及中间涉及到的各个概念。
启动第一个Docker容器
我的本地环境是Mac OS,选择的Docker安装包是Docker for Mac (macOS)。安装完成后,可以通过以下命令测试Docker是否安装成功。
~ docker --versionDocker version 18.09.0, build 4d60db4
Docker文档中给出的示例镜像是:hello-world。通过docker run
命令启动第一个Docker容器。
~ docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. ...For more examples and ideas, visit: https://docs.docker.com/get-started/
这里涉及到两个概念:镜像和容器。目前可以先简单的将镜像理解为Java语言中的类,将容器理解为对象。Docker通过镜像创建容器,一个镜像可以创建多个容器。
通过docker image ls
可以查看本地的镜像:
~ docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEhello-world latest 4ab4c602aa5e 3 months ago 1.84kB
通过docker container ls -a
可以查看容器:
~ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 541e363b4097 hello-world "/hello" 7 minutes ago Exited (0) 7 minutes ago zen_swanson
541e363b4097 就是上面通过docker run hello-world
启动的容器。
如何定义一个自己的镜像
这里需要先介绍一下Dockerfile。Dockerfile由“基础镜像”和一组命令组成。这样说会有一些抽象难以理解,可以先看一个Dockerfile例子:
# 基础镜像为java:8FROM java:8# 将当前目录的文件拷贝到容器的/home/user/app目录COPY . /home/user/app# 设置当前目录为/home/user/app,等同于cd /home/user/appWORKDIR /home/user/app# 执行javac命令,编译拷贝过来的Hello.javaRUN javac Hello.java# 执行java命令,运行Hello代码CMD ["java", "Hello"]
对应代码:
public class Hello{ public static void main(String[] args){ System.out.println("hello world!!!"); } }
目录结构:
my-docker lltotal 16-rw-r--r-- 1 yingong staff 101B 12 23 23:21 Dockerfile-rw-r--r-- 1 yingong staff 106B 12 23 22:31 Hello.java
Docker文档中是一个Python的例子,我这里换成了一个Java版本的,可以互相参考。有了Dockerfile,我们就可以通过Dockerfile构建出我们自己的镜像。
my-docker docker build -t hello:0.0.1 . Sending build context to Docker daemon 15.87kB Step 1/5 : FROM java:8 ---> d23bdf5b1b1b Step 2/5 : COPY . /home/user/app ---> 11d307e7dfa5 Step 3/5 : WORKDIR /home/user/app ---> Running in bde0589938cd Removing intermediate container bde0589938cd ---> 9f2c2cf0481f Step 4/5 : RUN javac Hello.java ---> Running in 82d3866d5d1d Removing intermediate container 82d3866d5d1d ---> c3c5c98c72a9 Step 5/5 : CMD ["java", "Hello"] ---> Running in 387930209ac6 Removing intermediate container 387930209ac6 ---> a28a3c1f1802 Successfully built a28a3c1f1802 Successfully tagged hello:0.0.1
再次执行docker image ls
,可以看到刚刚构建的hello:0.0.1
镜像:
my-docker docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEhello 0.0.1 a28a3c1f1802 3 minutes ago 643MB
使用构建的镜像创建一个容器:
my-docker docker run hello:0.0.1hello world!!!
Service
Service又是什么呢?在Docker文档中举了个例子。假设现在有一个视频分享网站,那么网站会有将应用数据存储到db的service,也会有将用户上传的视频文件在后台进行转码的service。我理解service是组成一个分布式系统的基础单位。
Docker为什么要搞出一个Service的概念?按上面的定义,假设有一个分布式系统包含登录、注册两个service,平时登录请求多,注册请求少。如何才能快速实现部署10个包含登录service的容器,2个包含注册service的容器?文档中给出的解决方案是docker-compose。
先看一下文档中给出的示例:
version: "3"services: web: # replace username/repo:tag with your name and image details image: username/repo:tag deploy: replicas: 5 resources: limits: cpus: "0.1" memory: 50M restart_policy: condition: on-failure ports: - "4000:80" networks: - webnet networks: webnet:
先记住几个关键属性:
image:指定使用哪个镜像
replicas:部署几个容器
cpus:分配的cpu资源
memory:分配的内存资源
ports:端口映射,将容器暴露的端口映射到宿主机上。4000是宿主机端口,80是容器端口。
这里我们还是使用一个springboot的镜像作为对比:
version: "3"services: web: image: springio/gs-spring-boot-docker deploy: replicas: 3 resources: limits: cpus: "1" memory: 512M restart_policy: condition: on-failure ports: - "4000:8080" networks: - webnet networks: webnet:
差别有3点:
镜像:替换为springio/gs-spring-boot-docker
memory:改成了512M,一开始忘记改了,沿用了文档中的50M,结果还没启动完内存耗尽容器就被kill了。
ports:springboot镜像的端口是8080
下面开始部署服务:
# 后面会解释这个命令的含义,目前先不用理解。docker swarm init# 通过docker-compose.yml部署一个名称为springboot的servicedocker stack deploy -c docker-compose.yml springboot
查看部署的服务,可以看到服务产生了3个容器:
~ docker service ls ID NAME MODE REPLICAS IMAGE PORTS qkr25qd45e8s springboot_web replicated 3/3 springio/gs-spring-boot-docker:latest *:4000->8080/tcp
现在可以尝试访问一下镜像中的服务,映射到宿主机的端口是4000:
~ curl -4 http://localhost:4000Hello Docker World%
Swarms
Swarms又是什么?想象一下公司的线上环境,假设我们公司有一个机房,机房里有30台物理机。现在我想部署10个包含登录服务的容器,怎么分配这些容器?Swarms负责的就是容器的管理、调度。
为了测试Swarms的,首先我们要创建2台虚拟机,模拟机房中的物理机器。
docker-machine create vm1 --virtualbox-boot2docker-url "https://github.com/boot2docker/boot2docker/releases/download/v18.06.1-ce/boot2docker.iso"docker-machine create vm2 --virtualbox-boot2docker-url "https://github.com/boot2docker/boot2docker/releases/download/v18.06.1-ce/boot2docker.iso"
上面的命令创建了vm1和vm2两台虚拟机,通过docker-machine
命令可以查看创建的虚拟机:
~ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS vm1 - virtualbox Running tcp://192.168.99.100:2376 v18.06.1-cevm2 - virtualbox Running tcp://192.168.99.101:2376 v18.06.1-ce
不要使用docker文档中的命令创建虚拟机,v18.09.0版本的boot2docker会存在端口映射问题。对应的issue。
创建虚拟机后,需要让两台虚拟机加入Swarms集群,变成Swarms节点:
# 将vm1设置主节点 负责处理容器调度命令docker-machine ssh vm1 "docker swarm init --advertise-addr 192.168.99.100" # 让vm2加入Swarms,token在上面命令的输入中获取docker-machine ssh vm2 "docker swarm join --token xxxx 192.168.99.100:2377"
做完上面的操作,我们可以先执行eval $(docker-machine env vm1)
命令将当前会话切换的虚拟机vm1上。执行docker node ls
命令应该可以看到:
~ docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONx1tbu3a6mnasogkg6q80fk4eh * vm1 Ready Active Leader 18.06.1-cekwd6sb94neosed9ngalihltda vm2 Ready Active 18.06.1-ce
现在Swarms已经准备好部署服务了,再次执行部署服务命令:
~ docker stack deploy -c docker-compose.yml hello ...## 已部署的服务 ~ docker service ls ID NAME MODE REPLICAS IMAGE PORTS yjtnmt2j5oxf hello_web replicated 3/3 springio/gs-spring-boot-docker:latest *:4000->8080/tcp## 产生的容器 ~ docker service ps yjtnmt2j5oxf ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS lfphgzdt1qml hello_web.1 springio/gs-spring-boot-docker:latest vm2 Running Running 8 hours ago zjoji8nthzei hello_web.2 springio/gs-spring-boot-docker:latest vm1 Running Running 8 hours ago t1qsyy8yjv1u hello_web.3 springio/gs-spring-boot-docker:latest vm2 Running Running 8 hours ago
可以看到3个容器,其中2个部署在了vm2上,1个部署在vm1上。
Swarms只是用来了解容器调度的概念,不需要深入学习。有兴趣的可以多了解下k8s。
Stack
终于到了最后一个概念:Stack。简单说一下,Stack和Service可以对比着看。Service中只有一个服务,Stack是服务的集合。A stack is a group of interrelated services that share dependencies, and can be orchestrated and scaled together。Stack是一组互相关联的服务,它们共享依赖,可以一起被编排和扩缩容。
作者:yingzong
链接:https://www.jianshu.com/p/7977705c5864