Docker入门

作者: yingzong | 来源:发表于2018-12-25 01:51 被阅读63次

工欲善其事,必先利其器。

最近想在本地搭建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搭建环境时继续分析。欢迎关注个人公众号随时交流技术。

相关文章

网友评论

    本文标题:Docker入门

    本文链接:https://www.haomeiwen.com/subject/bjcxlqtx.html