• Docker进阶


    Docker进阶

    Dockerfile

    Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于基础(原始)镜像并最终创建一个自定义的新的镜像。

    常用命令
    命令 作用
    FROM image_name:tag 定义了使用哪个基础镜像启动构建流程
    MAINTAINER user_name 声明镜像的创建者
    ENV key value 设置环境变量 (可以写多条)
    RUN command 是Dockerfile的核心部分(可以写多条)
    ADD source_dir/file dest_dir/file 将宿主机的文件复制到容器内,如果是一个压缩文件,将会在复制后自动解压
    COPY source_dir/file dest_dir/file 和ADD相似,但是如果有压缩文件并不能解压
    WORKDIR path_dir 设置工作目录
    EXPOSE port1 prot2 用来指定端口,使容器内的应用可以通过端口和外界交互
    CMD argument 在构建容器时使用,会被docker run 后的argument覆盖
    ENTRYPOINT argument 入口点,容器启动后会执行的命令,比如启动mysql。效果和CMD相似,但是并不会被docker run指定的参数覆盖
    VOLUME 将本地文件夹或者其他容器的文件挂载到容器中

    使用Dockerfile创建镜像

    目标:创建一个安装有jdk1.8的centos的docker基础镜像。

    1. 首先创建一个目录,用来存放脚本资源等:
    mkdir -p ~/dockerjdk8
    
    1. 下载jdk-8u241-linux-x64.tar.gz并上传到服务器(虚拟机)的该文件夹中,这里放个百度云的链接,提取码:14a1

    2. 创建文件Dockerfile(必须叫这个名字,区分大小写)
      文件内容如下:

    #依赖的基础镜像名称和ID,如果本地不存在,则自动下载
    FROM centos:7.6.1810
    #指定镜像创建者信息
    MAINTAINER EXAMPLE_NAME
    #切换当前的工作目录,进入容器化,默认进入的目录
    WORKDIR /usr
    #容器中创建java的目录
    RUN mkdir /usr/local/java
    #将容器外的文件(相对、绝对路径均可)拷贝到容器内指定的目录,并自动解压
    ADD jdk-8u241-linux-x64.tar.gz /usr/local/java/
    #容器中配置java环境变量
    ENV JAVA_HOME /usr/local/java/jdk1.8.0_241
    ENV JRE_HOME $JAVA_HOME/jre
    ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
    ENV PATH $JAVA_HOME/bin:$PATH
    

    基础镜像暂无需设置入口点。
    这里稍微解释一下入口点:

    有些容器启动后会直接在前台运行命令,导致你进入容器后不能做其他的事情。这时你可以使用run命令下的参数--entrypoint,它的作用是覆盖镜像的默认入口点,示例如下:
    启动一个以python3.6为基础镜像的容器:

    [root@docker ~]# docker run -it --name python3.6 python:3.6
    Python 3.6.5 (default, Mar 31 2018, 01:15:58) 
    [GCC 4.9.2] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 
    >>> 
    >>>
    

    容器启动后,直接进入python的交互终端了,不能做其它操作,而exit后,就直接退出容器了。这里加入--entrypoint bash参数,用bash覆盖它默认的入口点。

    [root@docker ~]# docker run -it --entrypoint /bin/bash --name python3.6 python:3.6
    root@430e3c9c09ac:/# 
    root@430e3c9c09ac:/# 
    

    这样进入容器后,就是bash交互了。

    1. 执行构建命令:
    docker build -t com.example/centos-jdk8 .
    #或
    docker build -t 'com.example/centos-jdk8' .
    

    不要忘记后面的空格和点

    1. 查看镜像是否完成docker images
    2. 创建容器:
    #退出容器命令行后,自动会删除该容器
    docker run -it --rm com.example/centos-jdk8 bash
    #退出容器命令后,容器自动停止但不会被删除
    docker run -it --name=mycentosjdk8 com.example/centos-jdk8 /bin/bash
    

    然后可以使用执行javajavacjava -version等命令测试环境。

    Docker私有注册中心

    私有注册中心搭建

    1. 拉取私有仓库镜像(此步可省略)
    docker pull registry:2.7.1
    
    1. 启动私有仓库容器
    docker run -d -p 5000:5000 --restart always --name myregistry registry:2.7.1
    

    restart参数:容器是否自动重启的策略。

    Docker容器的重启策略是面向生产环境的一个启动策略,在开发过程中可以忽略该策略。Docker容器的重启都是由Docker守护进程完成的,因此与守护进程息息相关。

    Docker容器的重启策略如下:

    • no,默认策略,在容器退出时不重启容器
    • on-failure,在容器非正常退出时(退出状态非0),才会重启容器
    • on-failure:3,在容器非正常退出时重启容器,最多重启3次
    • always,在容器退出时总是重启容器
    • unless-stopped,在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器
    1. 查看检验是否安装启动成功,打开一下网址:
    //IP为虚拟机(服务器地址)
    http://192.168.48.130:5000/v2/_catalog
    

    看到{"repositories":[]} 即代表仓库搭建成功,并且内容为空。

    上传镜像至私有中心

    1. 修改daemon.json,让docker信任私有仓库地址
    {
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
    ,"insecure-registries":["192.168.48.130:5000"]
    }
    

    注意此文件如果有错误,会导致docker服务无法启动,并报如下错误:

    Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details.
    
    1. 重启docker服务和私服容器:
    systemctl restart docker
    #如果创建容器时带有--restart always,下面这句可以省略
    docker start registry
    
    1. 修改镜像的相关信息,重打标记
    #创建标记(tag)此镜像为私有仓库的镜像
    docker tag com.example/centos-jdk8 192.168.48.130:5000/centos-jdk8 
    

    打标记从某种意义上说就是给一个镜像再加一个名字。

    重启私服容器:

    docker start registry
    
    1. 上传镜像:
    docker push 192.168.48.130:5000/centos-jdk8
    

    然后访问镜像列表(http://192.168.48.130:5000/v2/_catalog)即可看到该镜像:

    {
        "repositories": [
            "centos-jdk8"
        ]
    }
    

    名称空间默认是仓库的名字,如192.168.48.130:5000/centos-jdk8,则默认在http://192.168.48.130:5000/v2中寻找centos-jdk8镜像。
    example.com/centos-jdk8,则默认在https://example.com:5000/v2中寻找centos-jdk8镜像。

    Docker数据持久化

    Docker数据持久化主要有两种方式:

    • bind mount
    • volume

    Docker的数据持久化即使数据不随着container的结束而结束,数据存在于host机器上——要么存在于host的某个指定目录中(使用bind mount),要么使用docker自己管理的volume(/var/lib/docker/volumes下)。

    bind mount

    该方式在docker早期就开始使用,用于将宿主机目录挂载到容器中。

    例如:将宿主机上当前目录下的host-data目录挂载到容器中的/container-data目录:

    #将宿主机的host-data目录挂载为容器的container-data目录
    docker run -it -v $(pwd)/host-data:/container-data alpine sh
    

    注意:

    • host机器的目录路径必须为全路径(准确的说需要以/或~/开始的路径),否则docker会将路径作为Volume处理
    • 如果host机器上的目录不存在,docker会自动创建该目录
    • 如果container中的目录不存在,docker会自动创建该目录
    • 如果container中的目录已经有内容,那么docker会使用host上的目录将其覆盖掉

    使用挂载可以很方便的在宿主机与容器之间进行文件交换,但是bind mount在不同的宿主机系统时不可移植的,比如Windows和Linux的目录结构是不一样的,bind mount所指向的host目录也不能一样。这也是为什么bind mount不能出现在Dockerfile中的原因,因为这样Dockerfile就不可移植了。

    volume

    Docker镜像是由多个文件系统(只读层)叠加而成。当我们启动一个容器的时候,Docker会加载只读镜像层并在其上(译者注:镜像栈顶部)添加一个读写层。如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏。当删除Docker容器,并通过该镜像重新启动时,之前的更改将会丢失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。

    因此在构建Image的时候操作行数越少,镜像越小

    为了能够保存(持久化)数据以及共享容器间的数据,Docker提出了Volume的概念。简单来说,Volume就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。

    可以通过两种方式来初始化Volume:docker run -vdocker volume create

    volume也是绕过container的文件系统,直接将数据写到host机器上,只是volume是被docker管理的,docker下所有的volume都在host机器上的指定目录下/var/lib/docker/volumes

    将myVolume挂载到容器/myData目录:

    docker run -it -v myVolume:/myData alpine sh
    

    这里的myVolume指的是volume的名字,在/var/lib/docker/volumes/固定位置。

    # 构析容器
    docker inspect 容器名字
    # 构析volume
    docker volume inspect volume名称
    [
        {
            "CreatedAt": "2020-03-10T22:13:24+08:00",
            "Driver": "local",
            "Labels": null,
            "Mountpoint": "/var/lib/docker/volumes/myVolume/_data",
            "Name": "myVolume",
            "Options": null,
            "Scope": "local"
        }
    ]
    

    可以看到"Mountpoint": "/var/lib/docker/volumes/myVolume/_data",即文件就在这个文件夹,目录不存在时docker会自动创建。

    也可以使用docker volume create myVolume2手动创建volume。

    需要注意的是,与bind mount不同的是,如果volume是空的而container中的目录有内容,那么docker会将container目录中的内容拷贝到volume中,但是如果volume中已经有内容,则会将container中的目录覆盖。

    Dockerfile中的volume

    在Dockerfile中,我们也可以使用VOLUME指令来申明contaienr中的某个目录需要映射到某个volume:

    #Dockerfile
    VOLUME /foo
    

    这表示,在docker运行时,docker会创建一个匿名的volume,并将此volume绑定到container的/foo目录中,如果container的/foo目录下已经有内容,则会将内容拷贝的volume中。即,Dockerfile中的VOLUME /foodocker run -v /foo alpine的效果一样。

    Dockerfile中的VOLUME使每次运行一个新的container时,都会为其自动创建一个匿名的volume,如果需要在不同container之间共享数据,那么我们依然需要通过docker run -it -v my-volume:/foo的方式将/foo中数据存放于指定的my-volume中。

    因此,VOLUME /foo在某些时候会产生歧义,如果不了解的话将导致问题。

    删除volume

    一般有以下几种情况删除卷:

    • 默认情况下docker rm my_container,删除容器不会删除卷,即不会影响相关的数据。在删除完容器后,需要再手动删除卷,命令为docker volume rm 卷名。如果容器正在使用该volume,则无法删除,提示卷正在使用,必须先删除容器,才而已删除卷。
    • 如果需要删除容器的时候,同时将卷删除,则可以使用-v参数,如:docker rm -v 容器。但要注意,“bind-mount” 类型的Volume不会被删除。一个“正常”的Volume,Docker会自动将指定Volume路径(如/some/path`)上的数据复制到由Docker创建的新的目录下,如果是“bind-mount”,Volume就不会这样做。
    • 如果是临时容器,在创建启动容器的时候,使用--rm参数,当容器退出销毁的时候,卷也会自动被删除。

    不管哪种情况,只能删除没有容器连接的Volume。连接到用户指定主机目录的Volume永远不会被docker删除。

    如果不删除Volume,那么/var/lib/docker/volumes目录下得到一些僵尸文件和目录,并且还不容易说出它们到底代表什么。

    即想要删除volume,需要先删除链接的容器(即使是停止的容器),然后再删除volume。

    volume共享

    容器之间可以共享volume:

    docker run --name my_container -v /some/path ...
    docker run --volumes-from my_container --name my_container2 ...
    

    上面的命令将告诉Docker从第一个容器挂载相同的Volume到第二个容器,它可以在两个容器之间共享数据。

    如果你执行docker rm -v my_container命令,而上方的第二容器依然存在,那Volume不会被删除,如果你不使用docker rm -v my_container2命令删除第二个容器,那它会一直存在。

    示例:

    #第一个容器
    docker run -it --rm -v /mydata --name=mycontainner01 bash:5.0.11 sh
    #第二个容器相当于继承了上一个容器的所有的映射
    docker run -it --rm --volumes-from mycontainner01 --name=mycontainner02 bash:5.0.11 sh
    

    Docker Compose

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

    下载地址为https://github.com/docker/compose/releases

    该页面有安装命令:

    curl -L https://github.com/docker/compose/releases/download/1.25.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
    

    显示版本即为成功

    对于Mac和windows用户来说,Docker for Mac 、Docker for Windows 和 Docker Toolbox 早已经集成了docker-compose,所以用户不需要分别再安装docker-compose了。

    卸载docker compose直接删除二进制文件即可:sudo rm /usr/local/bin/docker-compose

    使用Docker Compose构建一个简单的web程序

    首先创建一个composetest目录:

    mkdir composetest
    cd composetest
    

    然后创建一个docker-compose.yml文件:

    touch docker-compose.yml
    vim docker-compose.yml
    

    文件中写入一下内容:

    #Compose文件的格式版本,需要和docker版本对应,这里使用3的系列版本
    version: '3'
    #定义若干的服务,每个服务就是一个容器实例,其子元素可以有N个,代表N个服务。
    services:
      #服务(实例)的名字,随意
      mywebservice:
        #指定为镜像名称或镜像ID。如果镜像在本地不存在,Compose将会尝试拉取这个镜像
        image: nginx:1.17.2
        #暴露端口信息
        ports: 
          #宿主机和容器的端口映射
          - "8888:80"
        #指定容器名称。默认将会使用项目名称_服务名称_序号这样的格式。随意
        container_name: "mynginx"
    #volumes:
    
    #networks:
    

    然后使用docker-compose up来启动服务:

    [root@localhost]# docker-compose up
    Starting mynginx ... done
    Attaching to mynginx
    

    服务便以交互式启动。启动时创建了一个虚拟网卡,然后创建了一个容器,接着启动。

    如果想要守护式启动,加上-d参数即可。

    可以使用docker-compose ps来查看compose服务。
    使用docker-compose exec 服务名 shell来进入服务。

    比如上面可以执行docker-compose exec mywebservice bash来进入服务的容器内部。

    停止服务:docker-compose stop 服务名
    启动服务:docker-compose start 服务名
    删除服务:docker-compose rm 服务名,服务必须停止之后才能删除。

    如果同时需要删除服务及其网络、数据卷,可以使用docker-compose down命令,会自动停止服务,并删除容器、网络、数据卷。

    划分网络

    我们可以同时创建多个服务,这些服务之间的网络可以是联通的,也可以是不联通的。要实现这个需求,我们可以对网络进行划分。

    目标:一次性创建3个Nginx服务,并且通过划分网络,来实现服务之间的互通。

    创建一个子文件夹,并创建docker-compose.yml文件,内容为:

    #Compose文件的格式版本,需要和docker版本对应,这里使用3的系列版本
    version: '3'
    #定义若干的服务,每个服务就是一个容器实例,其子元素可以有N个,代表N个服务。
    services:
      #服务(实例)的名字,随意
      mywebservice1:
        #指定为镜像名称或镜像ID。如果镜像在本地不存在,Compose将会尝试拉取这个镜像
        image: nginx:1.17.2
        #暴露端口信息
        ports: 
          #宿主机和容器的端口映射
          - "8001:80"
        #指定容器名称。默认将会使用项目名称_服务名称_序号这样的格式。随意
        container_name: "mynginx1"
        # 指定使用什么网络
        networks:
          - dev
        #卷映射挂载
        volumes:
          - myntfs:/usr/share/nginx/html
      mywebservice2:
        image: nginx:1.17.2
        ports: 
          - "8002:80"
        container_name: "mynginx2"
        networks:
          - dev
          - prod
      mywebservice3:
    #   这里可以使用build来构建Dockerfile镜像
        build: .
        ports: 
          - "8003:80"
        container_name: "mynginx3"
        networks:
          - prod
    
    #网络配置定义
    networks:
      #定义开发环境下使用的网络方式为桥接
      dev:
        driver: bridge
      #定义生产环境下使用的网络方式为桥接
      prod:
        driver: bridge
    
    #卷配置定义
    volumes:
      #定义ntfs的驱动
      myntfs:
        driver: local
    

    项目部署

    手动部署

    一般情况下都是打jar包(或者war包)来进行部署,然后在服务器上使用java -jar jar包名来运行,不过也可以使用Docker来进行快速部署。

    使用Docker部署

    手动Docker

    首先检查Java环境、MySQL,如果用到了Redis也需要配置。
    在Idea中maven的lifeCycle执行clean、package打包。

    创建一个Dockerfile,写入以下内容:

    #依赖的基础镜像名称和ID,如果本地不存在,则自动下载
    FROM 192.168.40.141:5000/centos-jdk8
    #指定镜像创建者信息
    MAINTAINER Bronya
    #切换当前的工作目录,进入容器化,默认进入的目录
    WORKDIR /
    #将容器外的文件(相对、绝对路径均可)拷贝到容器内指定的目录,并自动解压
    COPY app.jar /
    #容器的入口命令,容器一启动就执行
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    

    然后执行构建:

    docker build -t example.com/base .
    

    生成镜像之后启动容器就可以了。

    Docker Maven插件半自动部署

    1. 修改宿主机的docker配置,让其可以远程访问(开启docker允许远程创建的功能)
    vim /lib/systemd/system/docker.service
    

    找到ExecStart=/usr/bin/dockerd的行,在其后面添加如下内容:

    -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
    

    修改后的为:

    ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
    

    增加了上述内容后,就允许任意ip(0.0.0.0),通过2375的端口(maven插件访问的端口),远程的来执行docker的命令。

    对于老的docker版本,这么修改

    开启docker远程执行的权限(旧版本)

    vi /etc/sysconfig/docker-network
    

    找到DOCKER_NETWORK_OPTIONS=的行,在其后面添加如下内容:

    -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
    

    修改后结果:

    DOCKER_NETWORK_OPTIONS=-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
    

    然后重新加载配置并重启服务:

    systemctl daemon-reload && systemctl restart docker
    

    此时便可以在http://192.168.48.130:2375/info查看信息。

    如果客户端安装了docker可以执行docker -H tcp://192.168.48.130:2375 images来查看镜像信息

    1. 修改项目的pom文件
    <build>
        <!--打包后的项目的文件名称-->
        <finalName>app</finalName>
        <!--<finalName>${project.artifactId}</finalName>-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- docker的maven插件,官网:https://github.com/spotify/docker-maven-plugin -->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>1.1.1</version>
                <configuration>
                    <!--生成的镜像名字-->
                    <imageName>192.168.48.130:5000/${project.artifactId}:${project.version}</imageName>
                    <!--基础镜像名称,相当于dockerfile中的FROM centos-jdk8 -->
                    <baseImage>192.168.48.130:5000/centos-jdk8</baseImage>
                    <!--入口点-->
                    <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
                    <!-- copy the service's jar file from target into the root directory of the image
                    要将生成的微服务的jar资源拷贝到哪里,这里配置的是容器的根目录下面,相当于ADD /app.jar /-->
                    <resources>
                        <resource>
                            <targetPath>/</targetPath>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                    <!--设置docker的服务地址,默认连接的docker主机为localhost:2375-->
                    <dockerHost>http://192.168.48.130:2375</dockerHost>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    以上配置会自动生成Dockerfile,内容如下:

    FROM 192.168.48.130:5000/centos-jdk8
    WORKDIR /
    ADD app.jar /
    ENTRYPOINT ["java","-jar","/app.jar"]
    

    在项目pom所在目录,cmd执行:

    mvn clean package -Dmaven.test.skip=true docker:build
    
    • clean:清理target。
    • package -Dmaven.test.skip=true:打包并跳过测试。
    • docker:build -DpushImage:基于dockerfile创建镜像,并上传镜像到私服中。如果暂时没有私服,则去掉-DpushImage这个参数。

    然后便可以在宿主机创建该镜像的容器并运行。

  • 相关阅读:
    【掉下巴】枪的制造现场
    不引入第三个变量交换两个变量的方法
    [转]科学计算经典算法
    [小练eVC]常用控件之微调按钮
    【收购】LSI 40亿美元并购Agere
    VB6.0不支持鼠标滚轮的解决办法
    一个简单的BP网络C语言程序
    [转]想成为嵌入式程序员应知道的0x10个基本问题
    [zt]关于左值"lvalue"和右值"rvalue"的一点理解
    [掉下巴]细数非洲大山的肘下亡魂
  • 原文地址:https://www.cnblogs.com/lixin-link/p/12492912.html
Copyright © 2020-2023  润新知