2021-09-03

Docker指令详解&最佳实践&面试问题

docker已成为现在流行的一个开源容器引擎,我们在工作中也经常要与其打交道。这篇文章将主要从以下四个大方面阐述作者对docker的理解,希望能帮助大家由浅入深的或者是查漏补缺的更好的使用docker,欢迎大家在评论区留言交流。

  • docker里的一些基本概念
  • docker常用指令详解
  • Dockerfile最佳实践
  • docker面试题

docker里的一些基本概念

  1. 镜像和容器的关系

    容器和镜像

    由图中可以看出,红色部分的只读层为镜像,而容器就是多层可读层+一层读写层。

  2. image镜像

    镜像是由一堆可读层组成,除了最下边一层,每层都有一个指向下层的指针。image由Dockerfile构建而来,每层都对应一个dockerfile指令,例如下边的Dockerfile,每个命令会创建一层镜像。

    FROM ubuntu:18.04
    COPY . /app
    RUN make /app
    CMD python /app/app.py
    
  3. container容器

    容器=镜像+读写层。可以通过docker run [image]从一个镜像运行一个容器。

  4. Dockerfile

    指定镜像的生成规则,可以通过docker build -f [Dockerfile]生成一个镜像。

  5. 容器是怎么来的

    容器生成图

docker指令

根据上一部分我们知道了一个容器是怎么来的,下边列出的是作者在平时工作中经常会使用到的一些指令以及详解。

  1. Dockerfile
    假如我们有如下Dockerfile,关于Dockerfile的详解会在下一部分。

    # syntax=docker/dockerfile:1
    FROM node:12-alpine
    copy hello.js ./hello.js      #hello.js内容为console.log('hello world');
    CMD ["node", "hello.js"]
    
  2. build

    我们可以通过build指令从Dockerfile生成一个image。

    • -t
      表示我想要生成image的name和一个可选的tag,格式为name:tag。
    • .
      注意:这里的 .并不是表示Dockerfile的位置,而是此次构建的context,后边会详解。那么大家可能注意到我们并没有指定Dockerfile的路径,因为build默认会从当前目录寻找一个叫
      Dockerfile的文件。如果你的Dockerfile不在当前路径呢,我们可以使用-f [path]来指定其路径。
    docker build -t hello:v1 .
    

    其他常用的和image相关的指令还有:

    • docker images:查看当前宿主机上所有的image
    • docker tag hello:v1 hello:v2:把image的tag从v1变成v2
    • docker rmi [image-id]:删除一个指定的image
  3. run

    docker run hello:v1我们可以把容器运行起来了。

    run

    当然docker run有很多参数,我经常用的有:

    • -d: 以detached mode运行
    • -p: 后加3000:3000即表示把容器内的3000端口发布到宿主机的3000端口上
    • -v: /src:/src,把宿主机的src目录绑定到容器内
    • -it: 以交互模式运行container

    其他常用的和container相关的指令还有:

    • docker ps -a:查看所有宿主机上的container以及其状态等信息
    • docker stop [container-id]:停止一个容器
    • docker rm [container-id]:删除一个容器,-f表示强制。

    Dockerfile常用指令详解

    下图列出的是笔者记录的需要常用的一些指令及其特点。

    Docker指令

    其中包括的要点有:

    1. ADD和COPY的区别,分别在什么时候使用:

      1. 所有的文件复制都使用copy命令
      2. add 的src可以是一个远程url,如果是可解压文件如 tar.gz, 会自动解压
    2. 数据的两种绑定方式,使用 docker inspect [container-id] 可以找到Mounts字段查看

      1. 目录绑定,type=bind
      2. 数据卷绑定,type=volume
    3. dockerfile里设置的VOLUME字段的作用:

      FROM ubuntu
      VOLUME ["/data1","/data2"] 
      

      注意:dockerfile里的volume只能指定容器内的目录,不能指定主机的目录,可以在docker run的时候通过-v 进行覆盖,如果不覆盖,会默认在/var/lib/docker/volumes/xxx创建匿名volume。所以dockerfile里的VOLUME字段的作用是为了防止用户忘记在运行容器时忘记将动态文件挂在为卷。

    4. CMD和ENTRYPOINT的区别:

      1. CMD可以作为ENTRYPOINT的参数,docker把CMD命令拼接到ENTRYPOINT之后
      2. CMD可以在run时候被参数覆盖,而ENTRYPOINT不可以
      3. ENTRYPOINT可以执行一个脚本,这样CMD的参数会被脚本接收
      4. 一般最佳实践是将ENTRYPOINT设置为容器的main command
      5. 多个CMD只有最后一个生效,同理ENTRYPOINT也一样。
    5. CMD和ENTRYPOINT的两种语法:

      1. EXEC写法:CMD [“executable”,“param1”,“param2”]
      2. SHELL写法:CMD command param1 param2
      3. 永远使用EXEC写法
    6. ENV和RUN export的区别:

      1. 通过ENV设置的会持续存在,包括multiStage的dockerfile

      2. RUN export只会在当前image层有效,如下例子:

        FROM centos:6
        ENV FOO=foofoo
        RUN export BAR=barbar
        RUN export BAZ=bazbaz && echo "$FOO $BAR $BAZ"
        

        build这个image,最后一步的输出结果为:

        Step 4/4 : RUN export BAZ=bazbaz && echo "$FOO $BAR $BAZ"
         ---> Running in eb66196b238d
        foofoo  bazbaz
        

        我们可以看到,FOO 是输出来了,因为ENV可以跨多层生效,BAR 没有输出,而BAZ输出了,是因为RUN export只存在于当前层。

    7. ARG命令

    8. ARG指定的参数不能在容器运行时读取

    9. ARG指定的参数可以在docker build时候通过—build-arg覆盖

    10. 对于multiStage的dockerfile,需要在FROM之后使用如下指令重新指定一下,这是因为ARG只在一当前它所指定的stage中生效

      ARG DARSHAN_VER=3.1.6
      
      FROM fedora:29 as build
      ARG DARSHAN_VER
      RUN curl -O "ftp://ftp.mcs.anl.gov/pub/darshan/releases/darshan-${DARSHAN_VER}.tar.gz" \
          && tar ..
      
      FROM fedora:29
      ARG DARSHAN_VER
      COPY --from=build "/usr/local/darshan-${DARSHAN_VER}" "/usr/local/darshan-${DARSHAN_VER}"
      ..
      
    11. WORKDIR

    WORKDIR a
    WORKDIR b
    WORKDIR c
    RUN pwd #a/b/c
    
    1. multiStage多阶段构建

      docker从17.5开始支持多阶段构建,是为了解决:

      1. 镜像层次多,体积过大,部署时间长
      2. 源代码可能泄露

      通过多个FROM指定多个构建阶段,前边阶段可能是多个builder,把最终需要delivery的binary或其他file 复制到最终镜像。

      FROM golang:alpine as builder
      
      RUN apk --no-cache add git
      
      WORKDIR /go/src/github.com/go/helloworld/
      
      RUN go get -d -v github.com/go-sql-driver/mysql
      
      COPY app.go .
      
      RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
      
      FROM alpine:latest as pr
      
      RUN apk --no-cache add ca-certificates
      
      WORKDIR /root/
      
      COPY --from=0 /go/src/github.com/go/helloworld/app .
      
      CMD ["./app"]
      

      可以通过如下指令build某个stage的image:

      docker build --target builder -t alexellis2/href-counter:latest .
      

    Dockerfile最佳实践

    1. keep small

      1. 使用合适的base image,例如openJDK代替Ubuntu + jdk;常用的linux镜像一般有Ubuntu,CentOS,Alpine,一般更推荐使用Alpine,其大小分别如下:
        Untitled

      2. 如果你要运行的包包含所有的依赖,那么可以使用空的image: scratch

        ...
        
        # 运行:使用scratch作为基础镜像
        FROM scratch as prod
        # 在build阶段复制可执行的go二进制文件app
        COPY --from=builder /go/release/app /
        # 启动服务
        CMD ["/app"]
        
      3. 多阶段构建,使用maven build java app,然后tomcat deploy

    2. 高效利用缓存,尽量把变化最小的放在前边。例如把需要下载的依赖和业务代码分开:

      1. 对于前端项目,可以

        ADD package.json ./
        RUN npm i
        ADD src ./
        RUN npm run build
        # 这样如果package.json不变,可以缓存前两步,不会重新执行npm i
        
      2. 对于golang项目,可以

        ADD go.mod go.sum ./
        RUN go mod download
        ADD server ./
        RUN go build app.go 
        
    3. 利用.dockerignore来减小build context,可以提高docker load context的速度,例如:

      # .dockerignore
      node_modules/
      .git/
      
    4. 指令串联,一方面可以减少image分层,缩小体积;另一方面避免达到最大层数(127)。

      例如多个RUN合并为一个:

      FROM debian:stretch
      
      RUN set -x; buildDeps='gcc libc6-dev make wget' \
      
       && apt-get update \
      
       && apt-get install -y $buildDeps \
      
       && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
      
       && mkdir -p /usr/src/redis \
      
       && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
      
       && make -C /usr/src/redis \
      
       && make -C /usr/src/redis install \
      
       && rm -rf /var/lib/apt/lists/* \
      
       && rm redis.tar.gz \
      
       && rm -r /usr/src/redis \
      
       && apt-get purge -y --auto-remove $buildDeps
      
    5. 执行yum install -y 的时候可以一次install多个工具,例如:

      yum install -y gcc gcc-c++ make
      
    6. 执行apt-get install -y的时候增加选项— no-install-recommends,去除非必须的依赖

    7. 使用RUN echo $(ls -al) 代替 RUN ls

      在实际使用的过程中发现ls不会输出内容,加上echo后可以了。不知道大家有没有遇到这种情况。

    8. 优先使用copy over add。

    Docker面试题

    1. Docker和虚拟机的区别

      传统虚拟机是虚拟化出来一套硬件,在其上运行操作系统,而docker是直接运行在宿主的内核,没有自己的内核,也没有硬件虚拟化,更加轻便。

      虚拟机

      Docker

    2. ADD和COPY的区别,见上边。

    3. docker-compose vs k8s vs docker swarm

      1. docker-compose: 单机node集群编排
      2. k8s:多主机集群
      3. swarm:多主机集群
    4. 还有其他一些,基本上文都覆盖到了,也欢迎大家补充。

总结

本文主要介绍了笔者在工程中的一些常见docker用法,可能需要有一些docker基础之后食用更佳,欢迎评论区留言讨论。欢迎关注公众号二蛋实验室, 聊一聊golang,react,前后端优化以及股票投资等,共同进步,握手。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用python中的pymsql完成如下:表结构与数据创建 1. 建立 `users` 表和 `orders` 表。 `users` 表有用户ID、用户名、年龄字段,(id,name,age) `orders` 表有订单ID、订单日期、订单金额,用户id字段。(id,order_date,amount,user_id) 2 两表的id作为主键,`orders` 表用户id为users的外键 3 插入数据 `users` (1, '张三', 18), (2, '李四', 20), (3, '王五', 22), (4, '赵六', 25), (5, '钱七', 28); `orders` (1, '2021-09-01', 500, 1), (2, '2021-09-02', 1000, 2), (3, '2021-09-03', 600, 3), (4, '2021-09-04', 800, 4), (5, '2021-09-05', 1500, 5), (6, '2021-09-06', 1200, 3), (7, '2021-09-07', 2000, 1), (8, '2021-09-08', 300, 2), (9, '2021-09-09', 700, 5), (10, '2021-09-10', 900, 4); 查询语句 1. 查询订单总金额 2. 查询所有用户的平均年龄,并将结果四舍五入保留两位小数。 3. 查询订单总数最多的用户的姓名和订单总数。 4. 查询所有不重复的年龄。 5. 查询订单日期在2021年9月1日至9月4日之间的订单总金额。 6. 查询年龄不大于25岁的用户的订单数量,并按照降序排序。 7. 查询订单总金额排名前3的用户的姓名和订单总金额。 8. 查询订单总金额最大的用户的姓名和订单总金额。 9. 查询订单总金额最小的用户的姓名和订单总金额。 10. 查询所有名字中含有“李”的用户,按照名字升序排序。 11. 查询所有年龄大于20岁的用户,按照年龄降序排序,并只显示前5条记录。 12. 查询每个用户的订单数量和订单总金额,并按照总金额降序排序。
06-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值