zoukankan      html  css  js  c++  java
  • Docker基础入门

    一、安装

    环境:Centos7

    1、卸载旧版本

    较旧的Docker版本称为dockerdocker-engine。如果已安装这些程序,请卸载它们以及相关的依赖项。

    sudo yum remove docker 
                      docker-client 
                      docker-client-latest 
                      docker-common 
                      docker-latest 
                      docker-latest-logrotate 
                      docker-logrotate 
                      docker-engine
    

    2、使用存储库安装

    注意:官方给了三种安装方式,这里我们选择最常用的存储库安装

    在新主机上首次安装Docker Engine之前,需要设置Docker存储库。之后可以从存储库安装和更新Docker。

    sudo yum install -y yum-utils
    
    # 注意:此处是大坑,可以自行换成阿里的源
    sudo yum-config-manager 
        --add-repo 
        https://download.docker.com/linux/centos/docker-ce.repo
        
    # 我们用阿里的    
    sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
     
    # 进行安装(如果不换源,速度会很慢)
    sudo yum install docker-ce docker-ce-cli containerd.io
    

    3、启动以及验证

    systemctl start docker   # 启动
    systemctl status docker  #  查看状态
    docker version  # 查看版本
    docker run hello-world   #  测试
    

    4、设置加速器(阿里云)

    注意:里面的地址每个人都有专属的地址。

    打开阿里云官网->控制台->搜索容器镜像服务->右下角找到镜像加速器

    sudo mkdir -p /etc/docker
    
    tee /etc/docker/daemon.json <<-'EOF'
    {
      "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
    }
    EOF
    
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    
    docker run hello-world
    

    5、卸载

    # 卸载Docker Engine,CLI和Containerd软件包
    sudo yum remove docker-ce docker-ce-cli containerd.io
    
    # 主机上的映像,容器,卷或自定义配置文件不会自动删除。要删除所有图像,容器和卷
    sudo rm -rf /var/lib/docker
    

    二、Docker三要素

    1、仓库(Repository)

    仓库:集中存放镜像的场所。

    注意:仓库(Repository)和仓库注册服务器(Registry)是有区别的,仓库注册服务器往往存放着多个仓库,每个仓库中又包含多个镜像,每个镜像有不同的标签(tag)

    仓库分为公开仓库(Public)和私有仓库(Private)两种形式。

    最大的公开仓库是Docker Hub(https://hub.docker.com),存放了数量庞大的镜像供用户下载,国内的公开仓库包括阿里云、网易云等。

    2、镜像(Image)

    一个只读模板,用来创建Docker容器,一个镜像可以创建很多个容器。

    容器与镜像的关系类似于面向对象编程中的对象和类

    Docker 面向对象
    容器 对象
    镜像

    3、容器 (Container)

    独立运行的一个或一组应用。

    容器使用镜像创建的运行实例。

    它可以被启动、开始、停止、删除。每个容器都是相互隔离的,保证安全的平台。

    可以把容器看作是一个简易版的Linux环境(包括root用户权限,进程空间,用户空间和网络空间等等)和运行在启动的应用程序。

    容器的定义和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。

    三、简单了解底层原理

    1、Docker是怎么工作的

    Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接收命令并管理运行在主机上的容器。

    2、Docker为什么比VM快?

    (1)Docker有着比虚拟机更少的抽象层,由于Docker不需要Hypervisor实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源,因此在CPU、内存利用率上Docker将会在效率上有明显的优势。

    (2)Docker利用的是宿主机的内核,而不需要Guest OS。因此当新建一个容器时,Docker不需要和虚拟机一样重叠加载一个操作系统内核,从而避免引寻、加载操作系统内核,返回比较费时费资源的过程。当新建一个虚拟机时,虚拟机软件需要加载Guest OS,这个新建过程是分钟级别的,而Docker由于直接利用宿主机的操作系统,因此新建一个Docker容器只需几秒钟。

    Docker容器 虚拟机(VM)
    操作系统 与宿主机共享OS 宿主机OS上运行虚拟机OS
    存储大小 镜像小,便于存储与传输 镜像庞大(vmdk,vdi等)
    运行性能 几乎无额外性能损失 操作系统额外的CPU、内存消耗
    移植性 轻便、灵活,适应于Linux 笨重,与虚拟化技术耦合度高
    硬件亲和性 面向软件开发者 面向硬件运维者

    四、相关命令

    1、帮助命令

    docker version  # 查看docker版本信息
    docker info  # 详细说明
    docker --help  # 帮助命令
    

    2、镜像命令

    (1)列出本地镜像
    docker images [OPTIONS] [REPOSITORY[:TAG]]
    
    # OPTIONS说明:
    	-a: 列出本地所有的镜像(含中间映像层)
        -q: 只显示镜像ID
        --digests: 显示镜像的摘要信息
        --no-trunc: 显示完整的镜像信息
    
    # 各个选项说明:
    # REPOSITORY:表示镜像的仓库源
    # TAG:镜像的标签
    # IMAGE ID:镜像ID
    # CREATED:镜像创建时间
    # SIZE:镜像大小	
    

    同一仓库源可以有多个TAG,代表这个仓库源的不同版本,我们使用REPOSITORY:TAG来定义不同的镜像,如果不指定一个镜像的版本标签,例如我们只使用ubuntu,docker将默认使用ubuntu:latest镜像。

    (2)查找镜像
    docker search [options] 某个xxx镜像名字  # 会在https://hub.docker.com上去查找
    
    docker search mysql --filter=STARS=3000  # 搜索星数大于等于3000的镜像
    
    # OPTIONS说明:
    # --no-trunc: 显示完整的镜像描述	
    # -s:列出点赞数不小于指定值的镜像
    # --automated: 只列出automated build类型的镜像
    
    (3)获取镜像
    docker pull 镜像名字[:TAG]  # 如果不写TAG,则默认获取latest,此时从我们配置的阿里云上获取镜像
    
    docker pull mysql  # 获取最新版本的mysql
    Using default tag: latest
    latest: Pulling from library/mysql
    852e50cd189d: Pull complete   # 分卷下载
    29969ddb0ffb: Pull complete 
    a43f41a44c48: Pull complete 
    5cdd802543a3: Pull complete 
    b79b040de953: Pull complete 
    938c64119969: Pull complete 
    7689ec51a0d9: Pull complete 
    a880ba7c411f: Pull complete 
    984f656ec6ca: Pull complete 
    9f497bce458a: Pull complete 
    b9940f97694b: Pull complete 
    2f069358dc96: Pull complete 
    Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
    Status: Downloaded newer image for mysql:latest
    docker.io/library/mysql:latest  
    # docker pull mysql 等价于docker pull docker.io/library/mysql:latest 
    
    docker pull mysql:5.7  # 下载指定版本的镜像
    5.7: Pulling from library/mysql
    852e50cd189d: Already exists  # 联合文件系统,已经存在的不会去重复下载
    29969ddb0ffb: Already exists 
    a43f41a44c48: Already exists 
    5cdd802543a3: Already exists 
    b79b040de953: Already exists 
    938c64119969: Already exists 
    7689ec51a0d9: Already exists 
    36bd6224d58f: Pull complete 
    cab9d3fa4c8c: Pull complete 
    1b741e1c47de: Pull complete 
    aac9d11987ac: Pull complete 
    Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
    Status: Downloaded newer image for mysql:5.7
    docker.io/library/mysql:5.7
    
    (4)删除镜像
    # 删除单个镜像
    docker rmi -f 镜像名/ID  # 如果是镜像名,后面不带TAG,则默认删除latest
    
    # 删除多个镜像
    docker rmi -f 镜像名1 镜像名2 ...
    docker rmi -f id1 id2 ...     // 注意:两种方式不能混用
    
    # 删除全部镜像
    docker rmi -f $(docker images -aq)   
    

    3、容器命令

    ​ 有镜像才能创建容器,这是根本前提(下载一个Centos镜像演示)

    (1) 新建并启动容器(交互式)
    docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
    
    docker run -it --name mycentos centos  // 如果不指定别名,系统会自动分配
    
    # OPTIONS说明:
    # --name 容器新名字:为容器指定一个名称
    # -d:后台运行容器,并返回容器ID,即启动守护式容器
    # -i: 以交互模式运行容器,通常与-t同时使用
    # -t: 为容器重新分配一个伪输入终端,通常与-i同时使用
    # -P: 随机端口映射
    # -p: 指定端口映射有以下四种格式
    		ip:hostPort:containerPort
    		ip::containerPort
    		hostPort:containerPort
    		containerPort
    
    # 测试
    [root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 启动并进入容器
    [root@783cb2f26230 /]# ls   # 在容器内查看
    bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    [root@783cb2f26230 /]# exit   # 退出容器
    exit
    [root@iz2zeaj5c9isqt1zj9elpbz ~]# 
    
    
    (2)列出当前所有正在运行的容器
    docker ps [OPTIONS]  # 不带OPTIONS,则只列出正在运行的容器
    
    # OPTIONS说明(常用):
    # -a:列出当前所有正在运行的容器以及历史上运行过的
    # -l:显示最近创建的容器
    # -n: 显示最近n个创建的容器
    # -q: 静默模式,只显示容器编号
    # --no-trunc:不截断输出
    
    (3)退出容器
    exit  // 直接关闭并退出容器
    

    重新打开一个终端,执行docker ps -l,会返回刚才我们创建的容器信息,并且STATUS会提示已经退出。

    那么可不可以在交互式,不关闭容器的情况下,暂时退出,一会又可以回来呢?

    Ctrl + P + Q
    
    # 执行后,我们将退出容器,回到宿主机,使用docker ps -l,我们会发现这个刚才退出的容器STATUS是Up状态。
    
    (4)启动容器
    docker start [OPTIONS] CONTAINER [CONTAINER...]
    
    # 同时可以启动多个容器,容器名和ID可以混用
    
    # OPTION说明(常用):
    # -i : 进入交互式,此时只能进入一个容器
    

    进入上面我们退出但是依然存活的容器

    docker start -i 186ae928f07c
    
    (5)重启容器
    docker restart [OPTIONS] CONTAINER [CONTAINER...]
    
    # OPTIONS说明:
    # -t :在杀死容器之前等待停止的时间(默认为10)
    
    (6) 停止容器
    docker stop [OPTIONS] CONTAINER [CONTAINER...]
    
    # OPTIONS说明:
    # -t :在杀死前等待停止的时间(默认为10)
    
    docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 强制关闭(相当于拔电源)
    
    # OPTIONS说明:
    # -s: 发送到容器的信号(默认为“KILL”)
    
    (7)删除容器
    docker rm [OPTIONS] CONTAINER [CONTAINER...]
    
    # OPTIONS说明:
    # -f :强制删除,不管是否在运行
    
    # 上面的命令可以删除一个或者多个,但是想全部删除所有的容器,那么我们要把全部的容器名或者ID都写一遍吗?
    
    docker rm -f $(docker ps -aq)  # 删除所有
    docker ps -aq | xargs docker rm -f
    
    (8) 启动守护式容器
    docker run -d 容器名/容器ID
    

    说明:我们使用docker ps -a命令查看,会发现刚才启动的容器以及退出了,这是为什么呢?

    很重要的要说明一点:Docker容器后台运行,必须要有一个前台进程,容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就会自动退出。

    这就是Docker的机制问题,比如我们现在运行WEB容器,以Nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可,例如service nginx start,但是这样做,Nginx为后台进程模式运行,导致Docker前台没有运行的应用。这样的容器后台启动后,会立即自杀因为它觉得它没事可做。所以最佳的解决方案是将要运行的程序以前台进程的形式运行。

    那么如何让守护式容器不自动退出呢?我们可以执行一直挂起的命令。

    docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
    
    (9) 查看容器日志
    docker logs [OPTIONS] CONTAINER
    
    # OPTION说明:
    # -t 加入时间戳
    # -f 跟随最新的日志打印
    # --tail 显示最后多少条
    
    (10) 查看容器内的进程
    docker top CONTAINER 
    
    (11) 查看容器内部细节
    docker inspect CONTAINER
    
    (12) 进入正在运行的容器并以命令行交互
    • exec在容器中打开新的终端,并且可以启动新的进程
    docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  
    
    docker exec -it CONTAINER /bin/bash   // 进入容器内部并交互
    
    # 隔山打牛
    docker exec -it 3d00a0a2877e ls -l /tmp 
    
    # 3d00a0a2877e是我上面跑的守护式容器
    # 进入容器,执行ls -l /tmp 命令后将结果返回给我宿主机,而用户的界面还是停留在宿主机,没有进入容器内部
    
    • attach直接进入容器启动命令的终端,不会启动新的进程
    docker attach CONTAINER
    
    # 注意,进入上面那个守护式容器后,我们会看到还是每隔两秒打印hello Negan,而且不能退出,只能重新打开一个终端,执行docker kill 命令
    
    (13)容器与主机间的文件拷贝
    docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器内的文件拷贝到宿主机
    docker cp 90bd03598dd4:123.txt ~
    
    docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主机上的文件拷贝到容器
    docker cp 12345.txt 90bd03598dd4:~
    

    练习

    练习一:Docker部署Nginx

    # 查找一个nginx镜像
    docker search nginx
    # 下载镜像
    docker pull nginx
    # 启动
    docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主机):80(容器端口)
    # 测试(浏览器访问)
    123.56.243.64:3344 
    

    五、可视化

    1、portainer

    Docker图形界面管理工具,提供一个后台面板供我们操作。

    docker run -d -p 8088:9000 
    --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer
    

    六、Docker镜像

    1、镜像是什么

    镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

    所有应用直接打包docker镜像,就可以直接跑起来。

    如何获取镜像
    • 从远程操作下载
    • 朋友拷贝
    • 自己制作一个镜像DockerFile

    2、镜像加载原理

    (1)联合文件系统

    UnionFS(联合文件系统):Union文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层一层叠加,同时可以将不同目录挂在到同一个虚拟文件系统下。联合文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

    特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

    (2)Docker镜像加载原理

    Docker的镜像实际上是由一层一层的文件系统组成,也就是上面所说的联合文件系统。

    bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

    rootfs(root file system),在bootfs之上,包含的就是典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,CentOS等等。

    对于一个精简的OS,rootfs可以很小,只需要包含最近本的命令,工具和程序库就可以,因为底层直接用Host的kernel,自己只需要提供rootfs就可以,由此可见对于不同的Linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs。

    3、分层理解

    (1)分层的镜像

    我们下载一个镜像,注意观察下载的日志输出,可以看到是一层一层在下载。

    为什么Docker镜像采用这种分层的结构?

    最大的好处,我觉得莫过于资源共享了,比如有多个镜像从相同的Base镜像构建而来,那么宿主机只需要在磁盘上保留一份Base镜像,同时内存中也只需要加载一份Base镜像,这样就可以为所有的容器服务了,而且镜像每一层都可以被共享。

    (2)理解

    所有的Docker镜像都始于一个基础镜像层,当进行修改或者增加新内容时,就会在当前镜像层之上,创建新的镜像层。假如基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像之上创建第二个镜像层,如果继续添加一个安全补丁,就会创建第三个镜像层。

    再添加额外的镜像层同时,镜像始终保持是当前所有镜像的组合。

    注意:Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部。

    ​ 这一层就是我们通常说的容器层,容器之下的都叫镜像层。

    4、commit

    docker commit 提交容器成为一个新的镜像
    
    docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG]
    

    七、容器数据卷

    如果数据在容器中,那么我们容器删除,数据就会丢失!

    需求:数据需要持久化。

    MySQL,容器删了,删库跑路?Docker容器中产生的数据,需要同步到本地。

    这就是卷技术,目录的挂载,将我们容器内的目录,挂载到Linux上。

    1、使用数据卷

    • 直接使用命令来挂载
    docker run -it -v 主机目录:容器内目录  # 双向绑定,一方改变另一方自动改变
    
    docker run -it -v /home/ceshi:/home centos /bin/bash
    
    docker inspect d2343e9d338a  # 进行查看
    
    /*
    ...
     "Mounts": [   # 挂载 -v 
                {
                    "Type": "bind",
                    "Source": "/home/ceshi",  # 主机内的路径
                    "Destination": "/home",   # 容器内的路径
                    "Mode": "",
                    "RW": true,
                    "Propagation": "rprivate"
                }
            ],
    ...
    */
    

    2、实战:安装MySQL

    思考:MySQL的数据持久化问题

    docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
    

    3、具名挂载和匿名挂载

    (1) 、匿名挂载
    docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器内的路径
    
    docker volume ls  # 查看所有volume的情况
    DRIVER              VOLUME NAME
    local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名挂载
    local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
    
    # 这种就是匿名挂载,我们在-v只写了容器内的路径,没有写容器外的路径。
    
    (2)、具名挂载
    docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名挂载
    
    docker volume ls
    DRIVER              VOLUME NAME
    local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
    local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
    local               juming  # 我们上面起的名字
    
    # 通过-v 卷名:容器内路径
    
    # 所有的docker容器内的卷,没有指定目录的情况下,都在/var/lib/docker/volumes目录下。
    docker inspect juming  
    [
        {
            "CreatedAt": "2020-12-09T00:22:54+08:00",
            "Driver": "local",
            "Labels": null,
            "Mountpoint": "/var/lib/docker/volumes/juming/_data",
            "Name": "juming",
            "Options": null,
            "Scope": "local"
        }
    ]
    
    

    我们通过具名挂载可以方便的找到我们挂载的卷,大多情况下我们都会使用具名挂载。

    (3)、拓展
    -v 容器内路径  # 匿名挂载
    -v 卷名:容器内路径 # 具名挂载
    -v /宿主机路径:容器内路径  # 指定路径挂载
    
    # 通过 -V 容器内路径:ro rw改变读写权限
    ro readonly # 只读
    rw readwrite # 可读可写
    
    # 一旦设置了只读,容器对我们挂载出来的内容就有限定了。文件只能通过宿主机来操作,容器无法操作。
    docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 
    

    4、数据卷容器

    容器间的信息同步。

    docker run -it -v /home --name c1 centos /bin/bash   # 启动c1,作为一个父容器(指定容器内挂载目录)
    docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 启动c2,挂载c1,此时在两个容器中/home中的操作就会同步
    
    
    # 下面的容器是我们自己创建的,可以参考下面dockerfile进行构建
    docker run -it --name docker01 negan/centos   # 启动一个容器作为父容器(被挂载的容器)  
    docker run -it --name docker02 --volumes-from docker01 negan/centos  #启动容器2,挂载容器1 
    docker run -it --name docker03 --volumes-from docker02 negan/centos  #启动容器3,挂载容器2
    # 上面的容器3也可以直接挂载到容器1上,然后我们进入任意一个容器,进入挂载卷volume01/volume02,进行操作,容器间的数据会自动同步。
    

    结论:

    容器之间配置信息的传递,数据卷容器的声明周期一直持续到没有容器使用为止。

    但是一旦持久到了本地,本地数据不会被删除。

    八、DockerFile

    1、初识DockerFile

    DockerFile就是用来构造docker镜像的构建文件,命令参数脚本。通过这个脚本,可以生成镜像。镜像是一层一层的,脚本是一个个命令,每个命令就是一层。

    # dockerfile1内容,所有命令都是大写
    
    FROM centos   # 基于centos
    
    VOLUME ["volume01","volume02"]   # 挂载数据卷(匿名挂载)
    
    CMD echo "----end-----"
    
    CMD /bin/bash
    
    
    # 构建
    docker build -f dockerfile1 -t negan/centos .  # -f 指定路径 -t指定名字,不加tag默认最新
    Sending build context to Docker daemon  2.048kB
    Step 1/4 : FROM centos
     ---> 0d120b6ccaa8
    Step 2/4 : VOLUME ["volume01","volume02"]
     ---> Running in 0cfe6b5be6bf
    Removing intermediate container 0cfe6b5be6bf
     ---> 396a4a7cfe15
    Step 3/4 : CMD echo "----end-----"
     ---> Running in fa535b5581fa
    Removing intermediate container fa535b5581fa
     ---> 110d9f93f827
    Step 4/4 : CMD /bin/bash
     ---> Running in 557a2bb87d97
    Removing intermediate container 557a2bb87d97
     ---> c2c9b92d50ad
    Successfully built c2c9b92d50ad
    Successfully tagged negan/centos:latest
    
    docker images  # 查看
    
    

    2、DokcerFile构建过程

    (1)、基础知识

    每个保留关键字(指令)都必须是大写字母

    执行从上到下顺序执行

    #表示注释

    每一个指令都会创建提交一个新的镜像层,并提交

    DockerFile是面向开发的,我们以后要发布项目,做镜像,就需要编写dockerfile文件。

    Docker镜像逐渐成为企业交付的标准。

    (2)、基本命令
    FROM  #这个镜像的妈妈是谁?(基础镜像,一切从这里开始构建)
    MAINTAINER # 告诉别人谁负责养他?(镜像是谁写的,指定维护者信息,姓名+邮箱)
    RUN  # 你想让他干啥?(镜像构建的时候需要运行的命令)
    ADD  # 给他点创业资金(复制文件,会自动解压)
    WORKDIR # 镜像的工作目录
    VOLUME  # 给他一个存放行李的地方(设置卷,容器内挂载到主机的目录,匿名挂载)
    EXPOSE  # 门牌号是多少?(指定对外端口)
    CMD   # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
    ENTRYPOINT # 指定这个容器启动时候要运行的命令,可以追加命令
    ONBUILD  # 当构建一个被继承DockerFile,这时就会运行ONBUILD指令
    COPY    # 类似ADD,将我们文件拷贝到镜像中
    ENV    # 构建的时候设置环境变量
    

    3、实战操作

    (1)创建一个自己的CentOS
    # vim Dockerfile
    
    FROM centos
    MAINTAINER Negan<huiyichanmian@yeah.net>
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    RUN yum -y install vim
    RUN yum -y install net-tools
    EXPOSE 80
    CMD echo $MYPATH
    CMD echo "---end---"
    CMD /bin/bash
    
    # 构建
    docker build -f Dockerfile -t negan/centos .
    
    # 测试
    docker run -it negan/centos
    [root@ffae1f9eb97e local]# pwd
    /usr/local   # 进入的是我们在dockerfile设置的工作目录
    
    # 查看镜像构建过程
    docker history 镜像名/ID
    
    (2)CMD与ENTRYPOINT区别

    两者都是容器启动的时候要执行的命令,CMD命令只有最后一个会生效,后面不支持追加命令,会被替代。ENTRYPOINT不会被替代,可以追加命令。

    CMD

    # vim cmd
    FROM centos
    CMD ["ls","-a"]
    
    # 构建
    docker build -f cmd -t cmd_test .
    
    # 运行,发现我们的ls -a命令生效
    docker run cmd_test
    .
    ..
    .dockerenv
    bin
    dev
    etc
    ......
    
    # 追加命令运行
    docker run cmd_test -l
    # 抛出错误,不能追加命令,原来的ls -a命令被-l替换,但是-l不是一个有效的命令
    docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: "-l": executable file not found in $PATH": unknown.
    
    # 追加完整的命令
    docker run cmd_test ls -al
    
    total 56
    drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
    drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
    -rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
    lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
    drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
    ......
    

    ENTRYPOINT

    # vim entrypoint
    FROM centos
    ENTRYPOINT ["ls","-a"]
    
    # 构建
    docker build -f entrypoint -t entrypoint_test .
    
    # 运行
    docker run entrypoint_test
    .
    ..
    .dockerenv
    bin
    dev
    etc
    
    # 追加命令运行
    docker run entrypoint_test -l
    
    total 56
    drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
    drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
    -rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
    lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
    drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
    drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
    ......
    

    4、实战构建tomcat

    (1)环境准备
     ll
    total 166472
    -rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
    -rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
    -rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
    -rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
    
    (2)构建镜像
    # vim Dockerfile (Dockerfile是官方建议命名)
    
    FROM centos
    MAINTAINER Negan<huiyichanmian@yeah.net>
    
    COPY readme.txt /usr/local/readme.txt
    
    ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
    ADD apache-tomcat-9.0.40.tar.gz /usr/local/
    
    RUN yum -y install vim
    
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    
    ENV JAVA_HOME /usr/local/jdk1.8.0_11
    ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
    ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
    ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
    
    EXPOSE 8080
    
    CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out
    
    # 构建
    docker -t tomcat .
    
    (3)启动容器
    docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat
    

    九、发布自己的镜像

    1、docker hub

    首先需要在DockerHub上注册自己的账号,并且确定这个账号可以登录。

    在我们的服务器上进行登录,登录成功后提交我们的镜像。

    # 登录
    docker login [OPTIONS] [SERVER]
    
    Log in to a Docker registry.
    If no server is specified, the default is defined by the daemon.
    
    Options:
      -p, --password string   Password
          --password-stdin    Take the password from stdin
      -u, --username string   Username
    
    
    # 登录成功后推送我们的镜像
    docker push [OPTIONS] NAME[:TAG]  
    
    Push an image or a repository to a registry
    
    Options:
          --disable-content-trust   Skip image signing (default true)
    
    docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名后的(前面加自己的用户名)
    docker push huiyichanmian/tomcat
    

    2、阿里云

    登录阿里云,找到容器镜像服务,使用镜像仓库。创建命名空间,创建镜像仓库。选择本地仓库。

    具体阿里云上有特别详细的步骤,这里不再赘述了。

    十、Docker网络

    1、理解docker0

    (1)查看本机网卡信息
    ip addr 
    
    # 本地回环地址
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
           
    # 阿里云内网地址
    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
        link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
        inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
           valid_lft 286793195sec preferred_lft 286793195sec
        
    # docker0地址
    3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
        link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
    
    
    (2)查看容器网卡信息

    我们获取一个tomcat镜像,用来测试。

    docker run -d -P --name t1 tomcat 
    
    docker exec -it t1 ip addr
    
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    # 我们发现容器启动时候会得到一个eth0@ifxxx的网卡,而且ip地址和上面的docker0里的是在同一网段。
    233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
        link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
        inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
           valid_lft forever preferred_lft forever
    
    (3)、再次查看本地网卡信息
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
        link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
        inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
           valid_lft 286792020sec preferred_lft 286792020sec
    3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
        link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
    # 我们发现多了一条网卡信息,而且与容器里面的网卡有某种对应关系。(233,234)
    234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    

    我们重复上面的操作,不难发现我们只要安装了docker,本地网卡里就会多一个docker0,而且我们每启动一个容器,docker就会给容器分配一个网卡,而且在本地也会多出一个网卡信息,与容器内的相对应。这就是veth_pair技术,就是一对虚拟设备接口,它们都是成对出现,一段连着协议,一段彼此相连。正因为有了这个特性,我们通常veth_pair充当了一个桥梁,连接各种虚拟网络设备。

    所有的容器不指定网络的情况下,都是由docker0路由的,docker会给我们的容器分配一个默认的ip。

    Docker使用的是Linux的桥接,宿主机中docker0是一个Docker容器的网桥。所有的网络接口都是虚拟的。

    只要容器删除,对应的网桥也响应的被删除。

    问题:我们每次重启容器,容器的ip地址会变,而我们项目中一些配置使用的固定ip,这时候也需要做相应的改变,那么我们可不可以直接设置服务名呢,下次重启时候,配置直接找服务名。

    我们启动两个tomcat,进行测试,互相ping其对应的名字,看是否能通?

    docker exec -it t1 ping t2
    ping: t2: Name or service not known
    # 答案是肯定的,不能识别t2,如何解决?
    # 我们使用--link进行连接
    docker run -d -P --name t3 --link t2 tomcat
    
    # 我们尝试使用t3来ping t2
    docker exec -it t3 ping t2
    # 我们发现竟然通了
    PING t2 (172.17.0.3) 56(84) bytes of data.
    64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
    64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
    ......
    

    那么--link做了什么呢?

    docker exec -it t3 cat /etc/hosts  # 我们来查看t3的hosts文件
    
    127.0.0.1	localhost
    ::1	localhost ip6-localhost ip6-loopback
    fe00::0	ip6-localnet
    ff00::0	ip6-mcastprefix
    ff02::1	ip6-allnodes
    ff02::2	ip6-allrouters
    172.17.0.3	t2 6bf3c12674c8  # 原因在这了,这里对t2做了标记,当ping t2时候,自动转到172.17.0.3
    172.17.0.4	b6dae0572f93
    

    3、自定义网络

    (1)查看docker网络
    docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    10684d1bfac9        bridge              bridge              local
    19f4854793d7        host                host                local
    afc0c673386f        none                null                local
    
    # bridge 桥接(docker默认)
    # host 与主机共享
    # none 不配置
    
    (2)容器启动时的默认网络

    docker0是我们默认的网络,不支持域名访问,可以使用--link打通。

     # 一般我们启动容器是这样子,使用的是默认网络,而这个默认网络就是bridge,所以下面两条命令是相同的
    docker run -d -P --name t1 tomcat  
    
    docker run -d -P --name t1 --net bridge tomcat 
    
    (3)创建网络
    # --driver bridge 默认,可以不写
    # --subnet 192.168.0.0/16 子网掩码
    # --gateway 192.168.0.1 默认网关
    docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
    
    docker network ls
    
    docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    10684d1bfac9        bridge              bridge              local
    19f4854793d7        host                host                local
    0e98462f3e8e        mynet               bridge              local  # 我们自己创建的网络
    afc0c673386f        none                null                local
    
    
    ip addr
    
    .....
    # 我们自己创建的网络
    239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
        link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
        inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
           valid_lft forever preferred_lft forever
    .....
    

    启动两个容器,使用我们自己创建的网络

    docker run -P -d --name t1 --net mynet tomcat
    docker run -P -d --name t2 --net mynet tomcat
    
    # 查看我们自己创建的网络信息
    docker network mynet inspect
    
    # 我们发现我们刚启动的两个容器使用的是我们刚创建的网络
    ......
    "Containers": {
                "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                    "Name": "t2",
                    "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                    "MacAddress": "02:42:c0:a8:00:03",
                    "IPv4Address": "192.168.0.3/16",
                    "IPv6Address": ""
                },
                "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                    "Name": "t1",
                    "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                    "MacAddress": "02:42:c0:a8:00:02",
                    "IPv4Address": "192.168.0.2/16",
                    "IPv6Address": ""
                }
            },
    .......
    

    那么使用自己创建的网络有什么好处呢?

    我们回到我们以前的问题,就是域名ping不通的问题上。

    docker exec -it t1 ping t2
    PING t2 (192.168.0.3) 56(84) bytes of data.
    64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
    ......
    
    docker exec -it t2 ping t1
    PING t1 (192.168.0.2) 56(84) bytes of data.
    64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
    ......
    

    我们发现域名可以ping通了,那么说明我们自定义的网络已经帮我们维护好了对应关系。

    这样不同的集群使用不同的网络,也能保证集群的安全和健康。

    4、网络连通

    现在有这样一个需求,t1,t2使用的是我们自己定义的网络,t3,t4使用的默认的docker0网络。那么现在t3和t1或者t2能否通信呢?

    我们知道,docker0的默认网关是172.17.0.1,mynet默认网关是192.168.0.1,他们直接属于不同的网段,不能进行通信。那么现在如何解决上面的问题呢?

    那么mynet能不能给t3分配一个ip地址呢?如果能分配,那么问题就应该可以得到解决。

    docker network connect [OPTIONS] NETWORK CONTAINER
    
    Connect a container to a network
    
    Options:
          --alias strings           Add network-scoped alias for the container
          --driver-opt strings      driver options for the network
          --ip string               IPv4 address (e.g., 172.30.100.104)
          --ip6 string              IPv6 address (e.g., 2001:db8::33)
          --link list               Add link to another container
          --link-local-ip strings   Add a link-local address for the container
       
      # 一个容器两个ip地址 
     docker network connect mynet t3   # 将t3加入到mynet网络中
     
     # 查看mynet信息
     docker network inspect mynet
     
     "Containers": {
    			......
                "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                    "Name": "t3",  # 我们发现了t3
                    "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                    "MacAddress": "02:42:c0:a8:00:04",
                    "IPv4Address": "192.168.0.4/16",
                    "IPv6Address": ""
                }
            },
            ......
    

    这时候,t3就能和t1,t2进行通信了。

    5、部署Redis集群

    # 创建网络
    docker network create redis --subnet 172.38.0.0/16
    
    # 通过脚本创建六个redis配置
    for port in $(seq 1 6); 
    do 
    mkdir -p /mydata/redis/node-${port}/conf
    touch /mydata/redis/node-${port}/conf/redis.conf
    cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
    port 6379 
    bind 0.0.0.0
    cluster-enabled yes 
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    cluster-announce-ip 172.38.0.1${port}
    cluster-announce-port 6379
    cluster-announce-bus-port 16379
    appendonly yes
    EOF
    done
    
    # 启动容器
    vim redis.py
    
    import os
    for i in range(1, 7):
        str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} 
        -v /mydata/redis/node-{}/data:/data 
        -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf 
        -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
        os.system(str)
    
    python reidis.py
    
    # 创建集群
    docker exec -it redis-1 /bash/sh  # 进入redis-1容器
    
    redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
    
    # 创建集群。。。。
    >>> Performing hash slots allocation on 6 nodes...
    Master[0] -> Slots 0 - 5460
    Master[1] -> Slots 5461 - 10922
    Master[2] -> Slots 10923 - 16383
    Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
    Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
    Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
    M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
       slots:[0-5460] (5461 slots) master
    M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
       slots:[5461-10922] (5462 slots) master
    M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
       slots:[10923-16383] (5461 slots) master
    S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
       replicates d63e90423a034f9c42e72cc562706919fd9fc418
    S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
       replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
    S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
       replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
    Can I set the above configuration? (type 'yes' to accept): yes
    >>> Nodes configuration updated
    >>> Assign a different config epoch to each node
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join
    ....
    >>> Performing Cluster Check (using node 172.38.0.11:6379)
    M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
       slots: (0 slots) slave
       replicates d63e90423a034f9c42e72cc562706919fd9fc418
    S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
       slots: (0 slots) slave
       replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
    M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
       slots: (0 slots) slave
       replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
    M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    
    
    # 进行测试
    redis-cli -c
    127.0.0.1:6379> cluster info
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:6
    cluster_size:3
    cluster_current_epoch:6
    cluster_my_epoch:1
    cluster_stats_messages_ping_sent:160
    cluster_stats_messages_pong_sent:164
    cluster_stats_messages_sent:324
    cluster_stats_messages_ping_received:159
    cluster_stats_messages_pong_received:160
    cluster_stats_messages_meet_received:5
    cluster_stats_messages_received:324
    

    十一、Docker Compose

    1、介绍

    Compose是Docker官方开源的项目,需要安装。

    Compose是用于定义和运行多容器Docker应用程序的工具。通过Compose,可以使用YAML文件来配置应用程序的服务。然后,使用一个命令,就可以从配置中创建并启动所有服务。

    使用Compose基本上是一个三步过程:

    1. Dockerfile保证我们的项目在任何地方运行
    2. docker-compose文件
    3. 启动

    2、快速开始

    (1)安装
    curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    chmod +x /usr/local/bin/docker-compose
    
    docker-compose --version
    # 安装成功
    # docker-compose version 1.27.4, build 40524192
    
    (2)使用
    # 为项目创建目录
    mkdir composetest
    cd composetest
    
    # 编写一段flask程序
    vim app.py
    
    import time
    
    import redis
    from flask import Flask
    
    app = Flask(__name__)
    cache = redis.Redis(host='redis', port=6379)  # 这里主机名直接用“redis”,而不是ip地址
    
    def get_hit_count():
        retries = 5
        while True:
            try:
                return cache.incr('hits')
            except redis.exceptions.ConnectionError as exc:
                if retries == 0:
                    raise exc
                retries -= 1
                time.sleep(0.5)
    
    @app.route('/')
    def hello():
        count = get_hit_count()
        return 'Hello World! I have been seen {} times.
    '.format(count)
        
    # 编写requirements.txt文件(不指定版本,下载最新)
    flask
    redis
    
    # 编写Dockerfile文件
    FROM python:3.7-alpine
    WORKDIR /code
    ENV FLASK_APP=app.py
    ENV FLASK_RUN_HOST=0.0.0.0
    RUN apk add --no-cache gcc musl-dev linux-headers
    COPY requirements.txt requirements.txt
    RUN pip install -r requirements.txt
    EXPOSE 5000
    COPY . .
    CMD ["flask", "run"]
    
    
    # 编写docker-compose.yml文件
    # 文件中定义了两个服务,web和redis,web是从Dockerfile开始构建,redis使用的是公共镜像
    version: "3.9"
    services:
      web:
        build: .
        ports:
          - "5000:5000"
        volumes:
          - .:/code
      redis:
        image: "redis:alpine"
        
    # 运行
    docker-compose up 
    

    3、搭建博客

    # 创建并进入目录
    mkdir wordpress && cd wordpress
    
    # 编写docker-compose.yml文件来启动,并创建了一个单独Mysql实例,具有用于数据持久性的卷挂载
    version: "3.3"
    
    services:
      db:
        image: mysql:5.7
        volumes:
          - db_data:/var/lib/mysql
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: somewordpress
          MYSQL_DATABASE: wordpress
          MYSQL_USER: wordpress
          MYSQL_PASSWORD: wordpress
          
      wordpress:
        depends_on:
          - db
        image: wordpress:latest
        ports:
          - "8000:80"
        restart: always
        environment:
          W0RDPRESS_DB_HOST: db:3306
          WORDPRESS_DB_USER: wordpress
          WORDPRESS_DB_PASSWORD: wordpress
          WORDPRESS_DB_NAME: wordpress
      volumes:
        db_data: {}
        
    
    # 启动运行
    docker-compose up -d
    

    十二、Docker Swarm

    1、环境准备

    准备四台服务器。安装docker。

    2、swarm集群搭建

    docker swarm COMMAND
    Commands:
      ca          Display and rotate the root CA
      init        Initialize a swarm   # 初始化一个节点(管理节点)
      join        Join a swarm as a node and/or manager  # 加入节点
      join-token  Manage join tokens   # 通过节点token加入
      leave       Leave the swarm    # 离开节点
      unlock      Unlock swarm
      unlock-key  Manage the unlock key
      update      Update the swarm  #更新
    
    # 首先我们初始化一个节点
    docker swarm init --advertise-addr + 自己的ip(这里用内网地址,主要是省钱)
    docker swarm init --advertise-addr 172.27.0.4
    
    # 提示我们节点创建成功
    Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.
    
    To add a worker to this swarm, run the following command:
    	
    	# 在其他机器上执行,加入这个节点
        docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377
    
    # 生成一个管理节点的token,
    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    
    # 我们在机器2上行加入节点的命令
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377
    
    # 我们在机器1上查看节点信息
    docker node ls
    # 我们发现一个管理节点,一个工作节点,状态都是就绪状态
    ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
    yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
    mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0
    
    # 现在将机器3也加入,同样是work节点
    # 到此为止,现在只有机器4没有加入,这时候我们想把它设置成管理节点。
    # 在机器1上执行生成管理节点的命令,在机器4上执行
    docker swarm join-token manager
    # 生成的命令在机器4上执行
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
    # 此时机器4也是管理节点了
    This node joined a swarm as a manager.
    
    # 至此我们可以在机器1或者机器4上进行其他操作了。(只能在管理节点上操作)
    

    3、Raft协议

    在前面的步骤,我们已经完成了双主双从集群的搭建。

    Raft协议:保证大多数节点存活才能使用。必须大于1,集群至少大于3台。

    实验1

    将机器1上的docker停止,宕机,现在集群中只有一个管理节点了。集群是否可用。

    #我们在机器四上查看节点信息
    docker node ls
    # 发现我们的集群已经不能用了
    Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded
    
    # 我们将机器1上的docker重启,发现集群又能用了,但是此时机器1上的docker已经不是leader了,leader自动转给机器4了
    

    实验2

    我们将工作节点离开集群,查看集群信息

    # 我们在机器2上执行
    docker swarm leave
    
    # 在机器一上查看节点信息
    docker node ls
    # 我们发现机器2状态是Down
    ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
    yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
    mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
    u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
    im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0
    

    实验3

    现在我们将机器2也设置成管理节点(此时集群有三个管理节点),随机down掉一个管理节点,集群是否正常运行。

    # 在机器2上运行
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
    
    # 将机器1的docker宕机
    systemctl stop docker
    
    # 在机器二上查看节点信息
    docker node ls
    # 集群正常,且可以看到机器1宕机了
    ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
    yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
    mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
    vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
    u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
    im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0
    

    4、弹性创建服务

    docker service COMMAND
    
    Commands:
      create      Create a new service  # 创建一个服务
      inspect     Display detailed information on one or more services  # 查看服务信息
      logs        Fetch the logs of a service or task  # 日志
      ls          List services   # 列表
      ps          List the tasks of one or more services   # 查看我们的服务
      rm          Remove one or more services  # 删除服务
      rollback    Revert changes to a service's configuration
      scale       Scale one or multiple replicated services  # 动态扩缩容
      update      Update a service  # 更新
    
    docker service create -p 8888:80 --name n1 nginx  # 创建一个服务,在集群中随机分配
    kj0xokbxvf5uw91bswgp1cukf
    overall progress: 1 out of 1 tasks 
    1/1: running   [==================================================>] 
    verify: Service converged 
    
    # 给我们的服务增加三个副本
    docker service update --replicas 3 n1
    
    # 动态扩缩容
    docker service scale n1=10   # 和上面updata一样,这个方便
    
    docker ps # 查看
    

    服务,集群中任意的节点都可以访问,服务可以有多个副本动态扩缩容实现高可用。

    5、使用Docker stack部署博客

    我们现在需要将上面的博客跑在我们的集群中,并且要开十个副本。

    # 编辑docker-compose.yml文件
    version: '3.3'
    
    services:
       db:
         image: mysql:5.7
         volumes:
           - db_data:/var/lib/mysql
         restart: always
         environment:
           MYSQL_ROOT_PASSWORD: somewordpress
           MYSQL_DATABASE: wordpress
           MYSQL_USER: wordpress
           MYSQL_PASSWORD: wordpress
    
       wordpress:
         depends_on:
           - db
         image: wordpress:latest
         deploy:
           replicas: 10
         ports:
           - "8000:80"
         restart: always
         environment:
           WORDPRESS_DB_HOST: db:3306
           WORDPRESS_DB_USER: wordpress
           WORDPRESS_DB_PASSWORD: wordpress
           WORDPRESS_DB_NAME: wordpress
    volumes:
        db_data: {}
    
    
    # 进行启动
    docker stack deploy -c docker-compose.yml wordpress
    
    # 查看
    docker service ls
    
  • 相关阅读:
    参数探测(Parameter Sniffing)与影响计划重用的SET选项
    The workbook can not be opened
    参数Sniffing问题
    Unable to connect SQL Server
    正则|和[]的区别
    form的target捕捉不到动态写入name的iframe
    windows下git bash乱码问题
    ie6,7下textarea等上方空白
    根据字数截取字符串,不能截断url
    浏览器hack
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/14121481.html
Copyright © 2011-2022 走看看