您好,欢迎访问代理记账网站
移动应用 微信公众号 联系我们

咨询热线 -

电话 15988168888

联系客服
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

Docker系统性入门(二)

文章目录

  • image
    • 获取
    • Dockerfile
      • 基础镜像
      • RUN
      • 文件复制
      • 变量和环境变量
      • CMD
      • Demo
      • 缓存
      • .dockerignore
      • 多阶段构建
      • 非root
  • 存储
    • Volume
    • Bind Mount
    • 共享

image

  • 镜像的创建和管理

获取

  • 镜像的获取有三种方式
    • 从公有或私有的仓库拉取
    • 用Dockerfile自己build
    • 从文件导入(离线)
    • commit
      1
  • 公有仓库,最常用的有两个
    • Docker Hub
    • 和红帽推出的Quay,注册一下
  • 镜像命令
    docker pull nginx:1.20.0	# 拉取特定版本
    docker pull quay.io/bitnami/nginx	# bitnami是所属组
    docker image ls
    docker image inspect imageID	# 查看镜像的详细信息,可以看到OS layer等
    # 这块命令都要带上image,不带默认是container,不过建议使用docker container ...
    docker image rm  # 没有容器使用才可
    
  • 离线获取镜像
    • 找个有镜像的机器,让它给你打包一份,命令:docker image save nginx:latest -o nginx.image名称+tag也可以定位一个image
    • 然后发送给你,再load:docker image load -i ./nginx.image
  • 通过container的commit功能获取
    • 如果我们在container中修改了某些功能,希望保留下来,存成新的镜像以后使用(Create a new image from a container’s changes)
    • 进入容器,修改内容(例如NGINX的index),再使用:docker commit nginx_d1 roykun/nginx:1.0,相当于创建新的分支,保存成镜像,原镜像不变(不是container不变)
    • 关于容器内没有vim等命令
      # 进入容器
      apt-get update
      apt-get upgrade
      apt-get install vim
      # 或者使用echo直接写入文件
      echo "<h1>Here is NGINX</h1>" > index.html
      more index.html
      
  • 通过Dockerfile构建
    • 这个比较复杂了单独说

Dockerfile

  • 是一个文本文件,存的是指令,用来构建image,有特定的语法规则
  • 看个例子:
    FROM ubuntu:21.04	# 基础镜像(Ubuntu环境)
    RUN apt-get update && \
        DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev	# 安装python环境
    ADD hello.py /	# 要执行的文件
    CMD ["python3", "/hello.py"]	# 执行文件
    
  • 使用 docker image build -t hello .构建,生成一个名为hello:latest的镜像
    2
    • 可以用-t指定具体的镜像名称,方便发布到dockerhub自己的私有仓库
    • 例如我的注册名称是roykun,可以命名为:roykun/hello:1.0
      • 因为注册名是唯一的,所以能直接定位你的repo推过去?并不是
      • 需要在本地login:
        4
    • 也可以复制并重命名镜像,使用:docker image tag hello roykun/hello:1.0
      3
    • 它们的ID相同,所以rm只能通过名称和tag啦,推到仓库:docker image push roykun/hello:1.0
    • 推上去后:docker pull roykun/hello是不行的,tag默认是latest
  • 接下来详细讲讲语法和相关技巧

基础镜像

  • 纠正一下上一篇的概念,image也有可能包含基础的操作系统,例如可以在centos宿主机上选择Ubuntu基础镜像(视具体需求而定)
    • 当然还是依赖宿主机的进程,但容器内又包含了完整的底层操作系统和上层应用(天内天)
  • 基础镜像的选择
    • 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的
    • 固定版本tag而不是每次都使用latest
    • 尽量选择体积小的镜像
  • 一般run容器的时候会带-t参数表示进入终端,如果我们使用了基础镜像(例如Ubuntu),会指定进入bash
    • 如果不使用,需要定义CMD进入bash,或者参数中动态定义CMD
    • 基础镜像不一定是操作系统镜像,FROM指定的都是

RUN

  • 做image时执行命令,安装软件下载文件等
    FROM ubuntu:21.04
    RUN apt-get update
    RUN apt-get install -y wget
    RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
    RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
    RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
    RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
    
  • 每个RUN产生一个layer(层),一个RUN可以包含多条命令
    # 可以发现,体积会小一些
    FROM ubuntu:21.04
    RUN apt-get update && \
        apt-get install -y wget && \
        wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
        tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
        mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
        rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
    
  • 可以使用docker image history ID查看layer,实际上一个docker命令就会产生一个layer,只不过有些不占大小,例如WORKDIR

文件复制

  • 可以使用COPYADD命令复制文件到镜像,但ADD会对文件自动解压缩
    FROM python:3.9.5-alpine3.13
    WORKDIR /app/
    ADD hello.tar.gz /app/
    
  • 常和WORKDIR命令结合使用,相当于cd,而且还会自动创建不存在的目录

变量和环境变量

  • 既然是语法,怎么可能没有变量?ARGENV都可以设置变量
    FROM ubuntu:21.04
    ENV VERSION=2.0.1
    # ARG VERSION=2.0.1
    RUN apt-get update && \
        apt-get install -y wget && \
        wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
        tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
        mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
        rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
    
  • 区别是作用域不同
    • ENV可以理解成设置在image中的环境变量
    • ARG就不行了,一个临时的传参而已
      4
  • 使用ARG还可以在build是可以动态指定,覆盖Dockerfile里的变量
    • 命令:docker image build -f .\Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .

CMD

  • CMD设置容器启动时的命令,如果定义了多个CMD,只有最后一个会被执行
  • 如果docker container run创建容器时指定了其它命令,则CMD命令会被忽略
    • docker run -idt ipinfo:latest ipinfo,最后这个ipinfo是CMD命令,直接覆盖掉Dockerfile里的
    • 插一句:docker image prune -a 删除目前没在使用的镜像
    • docker container prune -a删除退出(stop)的容器
  • 一般创建容器后会进入shell,是因为在Ubuntu的基础镜像里有定义CMD
    • 使用history可以看到: /bin/sh -c #(nop) CMD ["/bin/bash"]
  • 如果写成CMD [],会覆盖掉基础镜像里的CMD,这会报错
  • ENTRYPOINT常和CMD一起使用,区别是它所设置的命令是一定会被执行
    # Dockerfile-entrypoint
    FROM ubuntu:21.04
    ENTRYPOINT ["echo", "hello docker"]
    
    • docker image build -f Dockerfile-entrypoint -t demo-entrypoint .
    • docker run -it --rm demo-entrypoint echo "hello fuck",rm表示容器退出(Exited)后自动删除
    • 创建容器会发现既输出docker又fuck
    FROM ubuntu:21.04
    ENTRYPOINT ["echo"]	# run时传入的内容会原样输出
    CMD []	# 虽然为空但不报错
    
  • Shell和Exec
    • 一般CMD都是用Exec格式的,就是中括号这个:CMD ["echo", "hello docker"]
    • 也可以使用Shell格式:ENTRYPOINT echo "hello docker"
    • 因为我们要执行的不一定都是Shell命令,所以推荐Exec
    # 区别是有的,还是在执行shell命令时,如果传参
    FROM ubuntu:21.04
    ENV NAME=docker
    CMD echo "hello $NAME"
    CMD ["sh", "-c", "echo hello $NAME"]	# 必须加sh -c,相当于换成shell格式
    

Demo

  • flask大家应该都接触过(我用过),做个运行flask demo的image
  • 要运行的文件
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        return 'Hello, World!'
    
  • Dockerfile
    FROM python:3.9.5-slim
    
    COPY app.py /src/app.py	# 这个必须精确到文件名,不然就会把src当做文件名而不是目录
    
    RUN pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple/
    
    # 工作目录!所有工作的出发点
    WORKDIR /src	# 切换到src下,进入容器后,包括后面的环境变量,都是从这下面开始
    ENV FLASK_APP=app.py	# 这个是环境变量,Linux中可以用export设置,是flask run要寻找的执行文件
    
    EXPOSE 5000	# 后面学,暴露端口
    
    CMD ["flask", "run", "-h", "0.0.0.0"]
    
  • 执行命令:docker image build -t flask-demo .docker run -idt --name flask_dc -p 5000:5000 flask-demodocker exec -it flask_dc /bin/sh
  • 在浏览器查看即可

缓存

  • build镜像的时候,如果之前biu过,会有cache,下次biu就很快
  • 但如果我们在cache前的部分修改代码,例如上面把app.py改了改,这会让下面缓存的部分全部失效
    5
    6
  • 那怎么整呢?把可能经常修改的这部分内容放到后面去,尽量使用cache
    FROM python:3.9.5-slim
    
    RUN pip install flask
    
    WORKDIR /src
    ENV FLASK_APP=app.py
    
    COPY app.py /src/app.py	# 放到需要cache的后面
    
    EXPOSE 5000
    
    CMD ["flask", "run", "-h", "0.0.0.0"]
    

.dockerignore

  • 看看前面的截图,build镜像时有个提示:Sending build context to docker daemon xxxKB
  • 最后那个 . 代表将当前目录的所有文件发到image里作为context
  • 很多是不需要的呀卧槽,类似git的ignore
    # 就在要做context的那个目录新建 .dockerignore文件
    ./vscode
    env/	# 啥不想拿进去,就写啥
    
  • 当然,也会影响COPY

多阶段构建

  • 例如我们要编译C代码得到二进制可执行文件,需要GCC镜像
    // hello.c
    #include <stdio.h>
    
    void main(int argc, char *argv[])
    {
        printf("hello %s\n", argv[argc - 1]);
    }
    
  • build:docker build -t hello .
    # Dockerfile
    FROM gcc:9.4	# 这个东西很大,一个多G
    
    COPY hello.c /src/hello.c
    
    WORKDIR /src
    
    RUN gcc --static -o hello hello.c
    
    ENTRYPOINT [ "/src/hello" ]
    
    CMD []
    
    Successfully tagged hello:latest
    $ docker image ls
    REPOSITORY     TAG          IMAGE ID       CREATED       SIZE
    hello          latest       7cfa0cbe4e2a   2 hours ago   1.14GB
    gcc            9.4          be1d0d9ce039   9 days ago    1.14GB
    
  • hello.c编译完以后,并不需要这样一个大的GCC环境,拿了二进制文件就跑
    # Dockerfile-new
    FROM gcc:9.4 AS builder	# 起个别名,这个阶段就是得到hello,其他不需要
    
    COPY hello.c /src/hello.c
    
    WORKDIR /src
    
    RUN gcc --static -o hello hello.c
    
    
    
    FROM alpine:3.13.5
    
    COPY --from=builder /src/hello /src/hello	# 把二进制文件拿过来
    
    ENTRYPOINT [ "/src/hello" ]
    
    CMD []
    
    docker build -t hello-apline -f Dockerfile-new .
  • 创建container试试 ,艺谋一样,这种情况就叫多阶段构建

非root

  • docker安装完毕之后,默认需要root用户的权限才能够启动和使用,这其实不好
    • 虽然用户可以sudo,但和root用户还是有区别的,用的是自己的ENV
    • 不安全主要是创建容器时使用-v参数导致,可能会对宿主机误操作,但目录映射一般无法避免
    • 如果容器内直接进root,对映射进去目录的操作权限就也是root
  • 但是它自动创建了docker用户组,可以将当前用户添加到这个组解决此问题
    # 如果当前用户不能sudo,需要到root编辑/etc/sudoers	chmod u+w /etc/sudoers
    sudo groupadd docker	# 可以看看到底有没  'docker' already exists
    # gpasswd 从组中添加/删除用户,-a:添加用户到组;-d:从组删除用户;
    sudo gpasswd -a roy docker # roy是你当前使用的用户,可以查看 /etc/group
    sudo systemctl restart docker
    su	# 再切回来
    su roy
    
    # 设置docker容器开机启动
    systemctl list-unit-files |grep enable	# 查看enable的服务
    systemctl enable docker.service
    docker run xxx --restart=always		# 设置xxx容器开机启动
    
    • 这种方法可以在宿主机非root操作docker,但是创建的容器还是直接到了root用户
  • 可以在build时为镜像创建用户/用户组,使用USER切换过去,这样创建的container也就是非root了
    FROM python:3.9.5-slim
    
    RUN pip install flask && \
    	# -r 创建系统工作组
    	# -r 建立系统帐号  -g 指定群组
        groupadd -r flask && useradd -r -g flask flask && \
        mkdir /src && \
        chown -R flask:flask /src	# 设置/src的拥有者
    
    USER flask	# 切换到flask用户
    
    COPY app.py /src/app.py
    
    WORKDIR /src
    ENV FLASK_APP=app.py
    
    EXPOSE 5000
    
    CMD ["flask", "run", "-h", "0.0.0.0"]
    
    • 这里创建的用户和用户组只会在容器中体现,我们也不能使用宿主机中的用户!例如roy:docker,只能挂载宿主机目录进去
      7

存储

  • 默认情况下,在运行中的容器里创建的文件,被保存在一个可写的容器层
    • 如果没有目录映射,创建的文件在容器删除后也会消失
    • 可写的容器层是和特定的容器绑定的,不方便的和其它容器共享
  • 这就需要数据持久化,一般有两种方式
    • Data Volume, 由Docker管理,(/var/lib/docker/volumes/ Linux), 持久化数据的最好方式
    • Bind Mount,由用户指定存储的数据具体mount在系统什么位置
      8

Volume

  • Dockerfile
    FROM alpine:latest
    RUN apk update
    RUN apk --no-cache add curl
    ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.1.12/supercronic-linux-amd64 \
        SUPERCRONIC=supercronic-linux-amd64 \
        SUPERCRONIC_SHA1SUM=048b95b48b708983effb2e5c935a1ef8483d9e3e
    RUN curl -fsSLO "$SUPERCRONIC_URL" \
        && echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \
        && chmod +x "$SUPERCRONIC" \
        && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
        && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
    COPY cron-date /app/my-cron
    WORKDIR /app
    
    VOLUME ["/app"]	# 注意看这里,定义一个持久化数据的目录
    
    # RUN cron job
    CMD ["/usr/local/bin/supercronic", "/app/my-cron"]
    
    • 使用VOLUME持久化数据
  • cron-date,一个定时执行的任务
    */1 * * * * date >> /app/test.txt
    
  • 构建:docker image build -t cron-date .,启动:docker run -d my-cron
    • 此时在/app下已经开始执行定时任务了,会看到输出文件
    • docker volume ls 可以看到创建的本地卷,docker volume inspect ID,查看详细信息
  • 还可以build时不指定VOLUME,而是创建容器时使用-v参数动态指定
    • docker run -d -v cron-data:/app cron-date,这里指定了宿主机中volume名称为cron-data(只能指定名称),但文件还在/var,:/app代表对容器中这app下面的data做持久化
    • 这就的-v,直接指定了Volume名字,和下面的Bind Mount不一样,卷的位置是系统指定的
    • 可以使用docker volume prune -f删除所有本地卷,慎用

Bind Mount

  • 这里的-v参数就是我们常用的目录映射了(最常用),将volume可以放在宿主机的指定位置,所以Windows用户也能查看了
  • 用之前的Dockerfile整一个:docker run -d --name bind-mount -v /home/roy/tmps:/app my-cron,也可以用${pwd}
  • 此时在指定目录就能看到持久化的文件了,这个操作的本质是挂载,所以用不着docker volume
  • 使用mount还可以帮我们搭建开发环境(借鸡生蛋)
    • 例如我们有一个gcc的镜像,如果要编译一个C文件,可以:docker run -d -v ${pwd}:/root gcc:9.4
    • 直接将我们要编译的文件挂载到gcc容器的root目录,进去编译!直接能映射出来
    • 类似的,在vscode中有个插件:Remote-Container,可以借助远程的container搭建需要的开发环境,自行探索!
  • 这两种方式有啥区别呢?
    • Volume可以远程,一般是备份吧,不在本地写
    • Mount就是里外来回造~,适合本地(宿主机)

共享

  • 容器之间共享数据,首先要创建个Volume放在公共机器上,大家都用这个卷存数据,就行
    1

  • 这里使用手动创建Volume的方式,需要安装Volume的driver

    • 准备两台机器试一下,一台装上driver(roy01),另一台放公共Volume做备份(roy)
    • 在roy01,驱动用docker plugin装,驱动:sshfs,可以用其他的(默认driver是local
    docker plugin install --grant-all-permissions vieux/sshfs
    # 这个192.168.109.3就是roy,放volume
    docker volume create --driver vieux/sshfs \
                          -o sshcmd=roy@192.168.109.3:/home/roy\
                          -o password=123456 \
                          sshvolume
    # 创建容器并使用这个volume
    docker run -idt -v sshvolume:/app busybox sh
    

    2

  • 在roy01的容器写东西,/home/roy 本地就能看到,OK
    3
    4


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进