嘘~ 正在从服务器偷取页面 . . .

Docker 学习


0. 背景

0.1 虚拟化技术的弊端

随着硬件的大力发展,除了运行一些大型软件之外,大部分情况硬件都处于空闲状态。除此之外,在一些特殊的开发情况下,可能需要使用不同版本的环境进行测试。有些甚至不能使用 Windows 系统,必须使用 Linux 系统。虽然 Windows 已经推出 Linux 的子系统——WSL,但是在不同环境之间的切换是个很大的问题,特别是在一台电脑上配置多环境的情况下。

这样硬件虚拟化技术就出现了,让用户能以单个物理硬件系统为基础创建多个模拟环境或专用资源。称为“Hypervisor” (虚拟机监控程序)的软件可直接连接到硬件,从而将一个系统划分为不同的、单独安全环境,即虚拟机(VM)(也可以称为,Guest OS)。这样我们就能从原有的系统中分配出多个可监控的,安全的子系统。

+-----+-----+-----+-----+
                             |App A|App B|App C|App D|
+-----+-----+-----+-----+    +-----+-----+-----+-----+
|App A|App B|App C|App D|    |Guest|Guest|Guest|Guest|
+-----+-----+-----+-----+    | OS0 | OS1 | OS2 | OS3 |
|Guest|Guest|Guest|Guest|    +-----+-----+-----+-----+
| OS0 | OS1 | OS2 | OS3 |    |        Hypervisor     |
+-----+-----+-----+-----+    +-----------------------+
|        Hypervisor     |    |         Host OS       |
+-----------------------+    +-----------------------+
|        Hardware       |    |        Hardware       |
+-----------------------+    +-----------------------+
          Type I                       Type II

Type I 是 Hypervisor 直接访问硬件资源,通常会有另一个操作系统运行于 Hypervisor 之上来对硬件资源,例如 VMware EXSi,Windows的Hyper-V,Linux 的 Xen;Type II 是 Hypervisor 和普通的应用一样,运行在某个操作系统(例如 Windows 或者 Linux 等,这里称之为宿主机操作系统,Host OS)之上,Hypervisor 通过 Host OS 访问硬件资源,例如 VMware Workstation,Virtual Box。

虚拟机的一个缺点在于 Guest OS 通常会占用不少硬件资源,并且性能损失的情况也比较严重。虽然不少 Hypervisor支持 动态内存,但基本上都会降低虚拟机的性能。如果说这样的资源占用少量的虚拟机还可以接受的话,同时运行十数台数十台虚拟机的时候,浪费的硬件资源就相当可观了。通常来说,其中相当大部分甚至全部 Guest OS 都是相同的。

0.2 Docker 问世

+-----+-----+-----+-----+                                   +-----+-----+-----+-----+
|App A|App B|App C|App D|     +-----+-----+-----+-----+     |App A|App B|App C|App D|
+-----+-----+-----+-----+     |App A|App B|App C|App D|     +-----+-----+-----+-----+
|+---------------------+|     +-----+-----+-----+-----+     |Guest|Guest|Guest|Guest|
||   Runtime Library   ||     |Lib A|Lib B|Lib C|Lib D|     | OS0 | OS1 | OS2 | OS3 |
|+---------------------+|     +-----+-----+-----+-----+     +-----+-----+-----+-----+
||       Kernel        ||     |    Container Engine   |     |        Hypervisor     |
|+---------------------+|     +-----------------------+     +-----------------------+
|   Operating System    |     |         Host OS       |     |         Host OS       |
+-----------------------+     +-----------------------+     +-----------------------+
|       Hardware        |     |        Hardware       |     |        Hardware       |
+-----------------------+     +-----------------------+     +-----------------------+
    Physical Machine                  Container                 Type II Hypervisor

上图中,每一个App和Lib的组合,就是一个容器。也就是Docker图标里面的一个集装箱。和虚拟机相比,容器有以下优点:

  1. 迅速启动:没有虚拟机硬件的初始化,没有Guest OS的启动过程,可以节约很多启动时间,这就是容器的“开箱即用”;
  2. 占用资源少:没有运行Guest OS所需的内存开销,无需为虚拟机预留运行内存,无需安装、运行App不需要的运行库/操作系统服务,内存占用、存储空间占用都小的多。相同配置的服务器,如果运行虚拟机只能运行十多台的,通常可以运行上百个容器毫无压力——当然前提是单个容器应用本身不会消耗太多资源。

当然,因为 Docker 公用内核的原因,只靠 cgroup 隔离,如果某一个容器导致内核崩溃,那么所有的容器都会崩溃。而对于彻底隔离应用的虚拟机,除非硬件或者 Hypervisor 出现问题,否则不会出现崩溃的问题。

当然如果还是不懂,可以拿别墅、公寓和胶囊式公寓类比(bushi)。

0.3 Docker 特性

有人说,Docker 的出现就像集装箱一样,所谓的穿着马甲的“标准化”。想要搞懂 Docker,需要明白它的两句口号。

  1. Build, Ship and Run 搭建、发送、运行三板斧

  2. Build once,Run anywhere 一次构建,随处运行

从这个口号我们就能够得到几个信息:

  1. 使用 Docker 时候,我们就可以避免配置文件的问题;
  2. 我们可以构建自己的镜像,并且将其打包到 Docker hub 上。这样别人就可以使用你的镜像了;
  3. 运行的容器可以随时进行更改或者停止。

Docker 技术的核心概念分别是

镜像(Image)别人存放好文档/环境的地方,只需要在 Docker hub 搜索并且下载,就即将可以使用。和 GitHub 共享代码类似,别人也会共享自己配置的镜像;

容器(Container)实现具体操作程序的地方,将本地的镜像放在容器中运行;

仓库(Repository) 自己本地下载别人的镜像,也可以把自己打包的镜像上传到 Docker hub。

就以运维中举例,我们可能会看到多种语言写出来的各种各样的东西,并且可能还有历史遗留的不同版本导致的错误。

这个时候,标准化管理显得尤其重要,这个时候就需要一个统一的操作方法。就算你只用 PHP/Java 写程序,PHP/JDK 的版本不同,加上 SQL 的不同,使用容器的不同,Nginx 或者 Apache,甚至有不知名的 Web 容器,还有自己写的 Web 容器。这样你就不可能在原生的 Linux 服务器中管理多个版本的环境。

1. Docker 镜像

在 Linux 上运行 Docker 需要 root 权限,千万注意。除此之外,Docker 是在外网的服务器上,所以需要进行换源,具体可以参考这个网站

+---------+  +---------+  +---------+    +-----+ +-----+ +-----+
| abc.com |  | def.com |  | xyz.com |    | DB1 | | DB2 | | DB3 |    
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+    
     |            |            |            |       |       |
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+    
| abc.com |  | def.com |  | xyz.com |    | DB1 | | DB2 | | DB3 |
| config  |  | config  |  | config  |    | conf| | conf| | conf|
|  data   |  |  data   |  |  data   |    | data| | data| | data|
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+
     |            |            |            |       |       |
     +------------+------------+            +-------+-------+
                  |                                 |
           +------+------+                   +------+------+          
           | Nginx Image |                   | MySQL Image |
           +------+------+                   +------+------+
                  |                                 |
                  +----------------+----------------+
                                   |
                            +------+-------+ 
                            | Alpine Image |
                            +------+-------+

从这里我们可以看到,Docker 的镜像可以在某个基础上再进行分装,对于我们使用 jar 包项目,我们可以使用 Dockerfile 进行配置。下面的内容会有说到如何使用 Dockerfile 打包一个 Java 项目。

1.1 拉取镜像

我们可以使用pull命令对一个镜像进行拉取

对于同一个镜像,都有一个 tag 对其进行区分,一般都是作为不同版本的管理。对于官方镜像。如果没有指定拉取的 tag,则会默认为 latest。

#默认会拉取最新版本的镜像
docker pull ubuntu
Using default tag:latest

#拉取18.04版本的 ubuntu
docker pull ubuntu:18.04

pull 命令中存在的子选项:

-a:是否获取仓库中的所有镜像,默认为否;

–disable-content-trust:取消镜像的内容校验,默认开启。

1.2 查看镜像

对于已经下载下来的镜像,我们可以使用docker images进行查看。

查看结果

images 命令存在的子选项:

-a:列出所有的镜像文件(包括临时文件),默认为否;

-q:仅输出 ID 信息,默认为否;

-f:过虑列出的镜像。

如果我们需要特别使用某个镜像,可以使用docker tag命令为本地镜像任意添加新的标签。

此时我们就获得了一个 myubuntu:0.1

如果你需要一个镜像的具体信息,可以使用docker inspect,会显示出包括创建时间、仓库 ID 等详细信息。

docker inspect ubuntu
#result
[
    {
        "Id": "sha256:54c9d81cbb440897908abdcaa98674db83444636c300170cfd211e40a66f704f",
        "RepoTags": [
            "chenxinyang621/ubuntu:0.1",
            "myubuntu:0.1",
            "ubuntu:latest"
        ],
        "RepoDigests": [
            "chenxinyang621/ubuntu@sha256:7c9c7fed23def3653a0da5bc9ecb651efe155ebd5802c7ba5d585edaa6c89496",
            "ubuntu@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-02-02T02:14:46.177066251Z",
        "Container": "3d4cc5cf7dc1af55a2be4440b5be4f96ea35516b98407a9b9446c218bb43818a",
        "ContainerConfig": {
            "Hostname": "3d4cc5cf7dc1",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"bash\"]"
            ],
            "Image": "sha256:b7032458728a84cd355ae42a8f7b323e29af86a22b211f4701363191b25fa805",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "bash"
            ],
            "Image": "sha256:b7032458728a84cd355ae42a8f7b323e29af86a22b211f4701363191b25fa805",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 72775208,
        "VirtualSize": 72775208,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/merged",
                "UpperDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/diff",
                "WorkDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:36ffdceb4c77bf34325fb695e64ea447f688797f2f1e3af224c29593310578d2"
            ]
        },
        "Metadata": {
            "LastTagTime": "2022-03-04T05:21:55.8145906Z"
        }
    }
]

1.3 搜索镜像

当然,我们可能需要确认我们需要的工具是否存在对应的 Docker 镜像,这个时候就要使用docker search

search 的子选项:

-f:过滤输出内容;

–limit:限制输出结果个数,默认为25个;

–no-trunc:不截断输出结果。

#查询所有 nginx 有关的镜像
docker search nginx

#限制查询10个
docker search --limit 10 nginx

#只查询官方的 nginx 镜像
docker search -f is-official=true nginx

查询结果

1.4 删除镜像

镜像删除使用docker rmi/docker image rm命令。当然如果是在 Windows 上的 Docker 已经提供可视化操作平台,但是我们还是要了解对应的命令。

不管是对于之后的容器删除还是现在的镜像删除,都有两种删除方式:

  1. 使用名称(+标签)删除。每个容器和镜像都有对应的名称,可以使用这个进行区分并进行删除;
  2. 使用 ID 删除,ID 是唯一值,所以使用 ID 也能进行删除操作(P.S. 可以不用输入完整 ID,只要能辨别出是唯一值即可)。

rmi 的子选项:

-f:强制删除镜像,即使存在容器依赖;

-no-prune:不清理未带标签的父镜像。

对于一些没有被使用的镜像,以及产生的残留临时镜像文件,可以使用docker image prune进行清除。

prune 的子选项:

-a:删除所有无用镜像,不只是临时镜像;

-filter:对清理对象进行过滤;

-f:强制删除镜像,不进行提示确认。

2. Docker 容器

如果认为虚拟机是模拟运行的一整套操作系统(包括内核 应用运行态环境和其他系统 环境)和跑在上面的应用 那么 Docker 容器就是独立运行的一个(或一组)应用,以及它们 必需的运行环境

2.1 简单的例子

Docker 允许你在容器内运行应用程序, 使用docker run命令来在容器内运行一个应用程序(容器)。当然run其实包含了一个create命令用来创建一个新的容器。

docker run ubuntu /bin/echo "Hello world"
Hello world

# 存在镜像之后,就不需要指定版本了
docker run ubuntu /bin/echo "Hello world"

docker: Docker 的二进制执行文件

run: 与前面的 Docker 组合来运行一个容器

ubuntu:15.10 指定要运行的镜像,Docker 首先从本地主机上查找镜像是否存在,如果不存在,Docker 就会从镜像仓库 Docker Hub 下载公共镜像

/bin/echo “Hello world”: 在启动的容器里执行的命令

2.2 启动容器

#先拉取一个 ubuntu 的镜像
docker pull ubuntu

#创建一个新的容器
docker create --name ubuntu1 -it ubuntu

#运行容器
docker start ubuntu1

#创建并运行一个容器
docker run --name ubuntu2 -it -d --restart=always ubuntu

-t:在新容器内指定一个伪终端或终端;

-i:允许你对容器内的标准输入 (STDIN) 进行交互;

–name:指定容器的名称,否则 Docker 会随机生成一段字符串;

-d:让容器在启动在启动时在后台运行;

–restart=always:设置容器随着 Docker 自启动。

之后我们可以使用 Ctrl+d/exit/Ctrl+w+d 退出容器,回到宿主机中。

2.3 停止容器

可以使用docker pause来暂停一个运行中的容器,使用docker pause来解除暂停一个运行中的容器。

如果需要终止一个容器,我们使用docker stop

docker ps -a让我们可以查看所有容器(默认只能查看运行中的容器);docker ps -qa只会显示 ID。

2.4 进入容器

有些时候我们需要进入容器修改一些设置,或者进行一些操作,我们需要使用exec

#参数的设置
docker exec -it ubuntu1 bash

-e:指定环境变量列表(在 MySQL Docker 配置中会有用到);

-u:执行明的用户名或 ID。

2.5 网络端口映射

在运行容器的过程中,我们需要对其绑定一个端口(Redis/MySQL/MinIO 的 Docker 操作就必需要这个步骤)

  • -P :**是容器内部端口随机**映射到主机的高端口;
  • -p :**是容器内部端口绑定到指定**的主机端口。

在绑定端口之前,还可以指定需要映射的地址,比如主机的127.0.0.1。

docker port命令可以让我们快捷地查看端口的绑定情况(绑定的端口默认都是 TCP,可以在后面使用 /UDP 绑定为 UDP )。

2.6 解决 docker ps 折行问题

docker ps 默认的显示内容过多,当值过长时就会导致折行,可读性很差,所以希望只显示自己关心的某些列。

可以自己指定显示的模板,例如:

docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"

可用的占位符:

名称 含义
.ID 容器ID
.Image 镜像ID
.Command 执行的命令
.CreatedAt 容器创建时间
.RunningFor 运行时长
.Ports 暴露的端口
.Status 容器状态
.Names 容器名称
.Label 分配给容器的所有标签
.Mounts 容器挂载的卷
.Networks 容器所用的网络名称

3. 配置 VSCode 访问 Docker

3.1 远程 ssh 连接

首先需要安装一个 remote ssh 插件,之后在侧边栏中选择进行连接。

对应插件

在服务器配置公钥之后,我们就可以直接连接登陆了

在远程安装 Docker 插件

3.2 访问拒绝

因为 Docker 需要 root 身份进行访问,所以正常使用 Docker 插件会出现权限问题。网上提供的方法大多数是开放 root ssh 权限,但这是非常非常危险的行为,特别是放置在公网上的云服务器。

这个时候可以将普通用户添加到 Docker 组中,之后重启服务器,即可访问。

sudo groupadd docker          #添加docker用户组
sudo gpasswd -a $USER docker  #将当前用户添加至docker用户组
newgrp docker                 #更新docker用户组

成功连接

4. Docker Compose

4.1 配置 Compose

安装最新的 Docker Compose,版本截止至2022.4.5(使用国内镜像源下载):

curl -L https://get.daocloud.io/docker/compose/releases/download/v2.4.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

添加可执行权限:

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

创建软链:

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

#检测是否安装成功
docker-compose --version

4.2 Dockerfile 配置

需要注意的是,每一次的执行都会创建一个新的层,过多的命令就会导致过多的层数,使得镜像过于膨胀。

#定制的镜像都是基于 FROM 的镜像,第一行必须
FROM **

#作者和邮箱
MAINTAINER ** <**@**>

# VOLUME 指定了临时文件目录为  /tmp,防止重要数据丢失
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp

#指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在
#因为 docker build 构建镜像每一个命令都是一层,除了通过 workdir 定义
WORKDIR /app

#把项目中的所有东西都复制到工作目录下面
COPY . .

# 将 jar 包添加到容器中并更名为 app.jar(按照 SpringBoot 构建方式为例)
# add 的功能和 copy 类似(在相同的需求下,官方更推荐 copy)
ADD target/*.jar /app/app.jar

#改变容器时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
	&& echo 'Asia/Shanghai' >/etc/timezone
#比使用两次 run 命令少一层构建

#仅仅只是声明端口
EXPOSE 10000

#也可以使用 cmd 命令
#如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。ENTRYPOINT 同理
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]
#这里的参数表示 `可执行命令`...`各个参数`

runcmd的区别在于,前者是 docker build 过程中执行的命令,后者是 docker run 的时候执行的命令。

ENTRYPOINT类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

(entrypoint 可以搭配 cmd 使用,相当于 cmd 给 entrypoint 传参。)

FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参 

转换成命令,相当于docker run nginx:test -c /etc/nginx/new.conf

4.3 yaml 解释

version: "3.7"
services:
 
  example:                     #服务名称
    image: tomcat:8.0-jre8     #使用的镜像
    container_name: example_container_name    #容器启动时的名字
    ports:                     #容器端口映射,前面的是宿主机端口,后面是容器内端口
      - "8080:8080"
    volumes:                   #容器中的哪个路径和宿主中的路径进行数据卷映射
      - /root/apps:/usr/local/tomcat/webapps     #手动映射
      - tomcatwebapps:/usr/local/tomcat/webapps  #自动创建数据卷映射,需要在后面声明数据卷
    networks:                  #指定容器启动使用的网桥
      - aa
    command: xxxxx             #用于覆盖容器默认启动指令
    envoriment:                #指定容器启动时的环境参数
      - xxxxx=xxxx
      
    #env_file:                 使用环境变量文件,书写格式和环境参数相同
    #  - ./xxx.env
    
    depends_on:                #设置这个服务依赖的服务名称(即启动优先级)
      #可以关联其他的服务
      - xxxxx
      
    #sysctls:                  #修改容器内部参数
    #  - xxxx=xxxx
    
volumes:
   tomcatwebapps:
   #external:       默认卷名会带上项目名(yml文件所在文件夹名),
   #  true          可以声明使用外部以存在的卷
   
networks:
   aa:                #创建逻辑同volume,将不同服务关联到一个网桥

4.4 示例

整体架构如下,Dockerfile 和 docker-compose.yml 放在同一层目录:

.
`-- freshcup
    |-- data
    |   |-- mysql
    |   |   `-- initsql
    |   |       `-- freshcup.sql
    |   `-- redis
    |       `-- conf
    |           `-- redis.conf
    |-- docker-compose.yml
    |-- Dockerfile
    `-- freshcup.jar

Dockerfile 的写法如下:

FROM openjdk:11

MAINTAINER cxy621 <cxyphp@gmail.com>

# VOLUME 指定了临时文件目录为/tmp。
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp

WORKDIR /app

COPY . .

ADD target/*.jar /app/app.jar

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

EXPOSE 10000

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]

docker-compose.yaml 配置如下:

version: "3.7"

services:

  freshcup:                        #自己写的 web 服务
    build:                         #根据指定的 dockerfile 构建镜像
      context:  .                  #指定 dockerfile 所在目录作为docker构建镜像的context环境
      dockerfile: Dockerfile       #指定 dockerfile 的文件,默认为 Dockerfile
    container_name: test_spring
    ports:
      - "10000:10000"
    networks:
      - freshcup
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:latest
    container_name: test_mysql
    ports:
      - "10001:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=123456
    volumes:
      - ./data/mysql/initsql:/docker-entrypoint-initdb.d    #用于初始化 mysql 数据库
      - ./data/mysql/db:/var/lib/mysql                      #mysql 数据文件挂载
      - ./data/mysql/conf:/etc/mysql                        #mysql 配置文件挂载
      - ./data/mysql/log:/var/log/mysql                     #mysql 日志文件挂载
    networks:
      - test_net

  reids:
    image: redis:latest
    container_name: test_redis
    ports:
      - "10002:6379"
    volumes:
      - ./data/redis/db:/data                                    #redis 数据文件挂载
      - ./data/redis/conf/redis.conf:/etc/redis/redis.conf       #redis 配置文件挂载
    command:
      - sh
      - -c
      - | #设置 redis 只读,并需要密码,
        redis-server /etc/redis/redis.conf                   
        redis-server --requirepass 123456                        
        redis-server --appendonly yes                            

networks:
  test_net:

在 idea 中配置好之后,执行就可以对此进行部署。

4.5 注意

如果 cmd 具有多个命令,不能直接通过 - 来执行,需要如下操作:

command:
    - sh
    - -c 
    - |
        cmd1
        cmd2
        cmd3
#这是串行的方式,命令

5. Docker Machine

Docker Machine 可以当作一个 Docker 的虚拟机,可以集中管理所有的 docker 主机,比如快速的给 100 台服务器安装上 docker。可以运行在本地或者云服务平台比如腾讯云。

示例图片

打开 git bash 进行操作:

# Linux 安装命令
base=https://github.com/docker/machine/releases/download/v0.16.0 &&
curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
sudo mv /tmp/docker-machine /usr/local/bin/docker-machine &&
chmod +x /usr/local/bin/docker-machine

# Windows 安装命令
base=https://github.com/docker/machine/releases/download/v0.16.0 &&
mkdir -p "$HOME/bin" &&
curl -L $base/docker-machine-Windows-x86_64.exe > "$HOME/bin/docker-machine.exe" &&
chmod +x "$HOME/bin/docker-machine.exe"

5.1 启动虚拟机

既然是虚拟机,那就需要一个虚拟机管理的系统作为 machine 的驱动,大部分的网站示例都是使用 ToolBox,我们这里使用 vmwareworkstastion。

在启动虚拟机时,实际上是从一个镜像文件中创建系统需要的文件,所以我们需要一个 ISO 文件放在C:\Users\<用户名>\.docker\machine\machines\cache中,下载链接

还需要一个驱动的 exe 文件,放在C:\Program Files\Docker\Docker\resources\bin中,下载链接

# 通过这个命令,创建一个名字为 test 的虚拟机
# 也可以使用 -d 参数
docker-machine create --driver vmwareworkstation test

# 通过 ls 查看我们目前已有的虚拟机
docker-machine ls

# 进入虚拟机内部
docker-machine ssh test

# 因为 docker 服务是在外网,我们需要提前换源
sudo vi /var/lib/boot2docker/profile

# 在 provider 下面一行输入以下内容(中科大源)
# 吐槽一句,国内某文章博客的横杠竟然是中文的,导致我换源失败……
--registry-mirror https://docker.mirrors.ustc.edu.cn/

sudo /etc/init.d/docker restart

# 换源之后查看 docker 配置文件信息
docker info 

# 运行 hello-world 测试 docker 情况
docker run hello-world

这里需要注意一点,进入 docker 虚拟机修改的时候,可能会有显示错位的问题(显示的位置和实际修改的位置差两个空白符作为的距离),这一点需要注意。

test 创建成功

内置 docker 服务的虚拟机创建完成

docker machine 常用命令

5.2 坑点

如果在使用 vmwareworkstation 驱动时候,出现No default Boot2Docker ISO found locally报错。说明是没有安装 Boot2Docker 的镜像文件,详细可以参考这篇文章的回答

6. Docker Swarm

Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。

swarm 集群由管理节点(manager)和工作节点(work node)构成。

  • swarm mananger:负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作;

  • work node:即图中的 available node,主要负责运行相应的服务来执行任务(task)。

原理示例图

6.1 构建主机

# 创建一个管理用的 docker 虚拟机
docker-machine create -d vmwareworkstation swarm-manager

# 使用 ssh 之后初始化 swarm 管理
# 使用的 ip 是创建之后虚拟管理系统分配的 ip
# 返回的结果是作为连接管理端和其他节点的重要命令,要保存好
docker swarm init --advertise-addr 192.168.114.134

# 创建两个 worker 作为测试,执行返回的命令,讲两个虚拟主机初始化为 manager 的节点
docker swarm join --token SWMTKN-1-4iyqlb1hkizqewx45wq8ixjqyysn39t7ouw808ybqq3gx4uo7n-93hhn1gh6fvqiojx8srkf9via 192.168.114.134:2377

返回解决

添加之后显示两个 worker 节点已经连接成功

6.2 测试服务

# 创建一个名为 helloworld 的服务
docker service create --replicas 1 --name helloworld alpine ping docker.com

# 查看特定服务的运行情况
docker service ps helloworld

# 查看服务的具体信息
docker service inspect --pretty helloworld

# 将服务扩展到两个节点
docker service scale helloworld=2

docker service rm helloworld

6.3 示例

给所有节点的服务拉取一个 redis 镜像:

docker service create --replicas 1 --name redis --update-delay 10s redis:3.0.6

# 将 redis 的版本滚动升级到最新的 latest
docker service update --image redis:latest redis

# 查看所有的节点
docker node ls

# 将 swarm-worker 激活
docker node update --availability active swarm-worker1

7. 启动错误

7.1 堆栈跟踪的末尾

仅限 Windows 平台的 Docker,原因是 wsl 2 内核出现问题,可能是在刚处于升级状态,暂时无法使用。

需要下载 NoLsp,之后执行 NoLsp.exe C:\Windows\System32\wsl.exe,success 之后就可以正常使用 wsl 2。

参考文章

  1. 如何通俗解释 Docker 是什么?
  2. 通俗易懂,一文带你了解什么是虚拟化技术
  3. Docker 教程 菜鸟教程
  4. Docker 技术入门与实战
  5. Docker — 从入门到实践
  6. Docker 命令自动补齐
  7. docker ps 显示指定的列
  8. Docker 部署 SpringBoot 项目
  9. 使用 Docker 部署 SpringBoot
  10. VSCode 远程连接 Docker 容器
  11. VSCode 中 Docker 插件无法连接
  12. docker-compose command 执行多条指令
  13. Docker for Windows 使用 VMware WorkStation
  14. Docker Toolbox 修改镜像源
  15. Docker Swarm 集群环境搭建及弹性服务部署
  16. docker Error An error occurred 引发异常的上一位置中堆栈跟踪的末尾

文章作者: 陈鑫扬
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陈鑫扬 !
评论
  目录