Docker入门
工欲善其事,必先利其器。
最近想在本地搭建Mysql主备、集群环境。之前的做法要么是本地起多个实例,绑定不同的端口;要么是创建多个虚拟机,但虚拟机资源占用高,搭建效率低。目前更轻量快速的方案是使用Docker,在Docker官网上有一个分为6章的《Get started with Docker》文档。本文基于文档整理了环境搭建过程以及中间涉及到的各个概念。
启动第一个Docker容器
我的本地环境是Mac OS,选择的Docker安装包是Docker for Mac (macOS)。安装完成后,可以通过以下命令测试Docker是否安装成功。
➜ ~ docker --version
Docker 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 ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-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:8
FROM java:8
# 将当前目录的文件拷贝到容器的/home/user/app目录
COPY . /home/user/app
# 设置当前目录为/home/user/app,等同于cd /home/user/app
WORKDIR /home/user/app
# 执行javac命令,编译拷贝过来的Hello.java
RUN javac Hello.java
# 执行java命令,运行Hello代码
CMD ["java", "Hello"]
对应代码:
public class Hello{
public static void main(String[] args){
System.out.println("hello world!!!");
}
}
目录结构:
➜ my-docker ll
total 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 ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello 0.0.1 a28a3c1f1802 3 minutes ago 643MB
使用构建的镜像创建一个容器:
➜ my-docker docker run hello:0.0.1
hello 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的service
docker 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:4000
Hello 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-ce
vm2 - 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 ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
x1tbu3a6mnasogkg6q80fk4eh * vm1 Ready Active Leader 18.06.1-ce
kwd6sb94neosed9ngalihltda 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是一组互相关联的服务,它们共享依赖,可以一起被编排和扩缩容。
本文介绍了如何构建自己的镜像、通过镜像启动容器,以及Service、Swarms、Stack分别是什么含义。更多内容会在后续《架构》专辑使用docker搭建环境时继续分析。欢迎关注个人公众号随时交流技术。