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