• 0

  • 462

  • Favorite

Docker的基本使用

智能的司机

我是老司机

2 months ago

Docker是一个开源的应用容器引擎,开发者可以打包应用和依赖包(运行环境)到一个可移植的镜像中,然后发布到任何一个Linux或者Windows上。容器之间是独立的沙盒机制,相互之间没有任何接口。

在项目开发中,总是会因为环境配置做许多重复性的工作,比如操作系统的配置,node环境的配置,各种依赖文件需要下载等,于是Docker出现,通过Dockerfile配置文件便可以将你所需要的环境生成出来,至此开发者可以不必再关心开发前与开发后的各种配置,仅专注于程序开发即可。

基本概念

  • 镜像(Image): 相当于一个root文件系统,比如一个ubuntu系统可一个作为一个镜像使用。镜像是用于创建Docker容器的模板。
  • 容器(Container): 用来承载你需要运行的项目的东西。镜像之于容器,就像JavaScript里的之于实例const container = new Image(),镜像是静态的定义,容器是镜像运行的实体。
  • 仓库(Repository): 用来保存镜像的仓库系统,比如你创建一个自己的专属镜像,然后上传到镜像仓库,上传后的镜像可以给其他人时候用。

应用场景

  • Web应用的自动化打包和发布。
  • 自动化测试和持续集成、发布。
  • 在服务型环境中部署和调整数据库或其他的后台应用。

创建一个基本的Docker应用

1. 下载镜像

# 查看已下载的镜像
docker images
# 在https://hub.docker.com/搜索镜像
docker search node
# 下载一个最新版本的node镜像,如果想选择版本,那么镜像名写node:16.04。
docker pull node
# 删除镜像,参数可以是镜像名称,也可以是镜像id
docker rmi node
# 强制删除
docker rmi -f node
复制代码

2. 创建Dockerfile文件

在项目根目录创建Dockerfile文件,根据一个基础镜像定制一个你想要的系统镜像,示例:

# 基础镜像
FROM node:latest

# 指定后续命令的用户
USER root

# 将当前目录下的所有文件(除了.dockerignore排除的路径)复制到镜像的/app目录
COPY . /app

# 指定后续的工作路径
WORKDIR /app

RUN npm i

# 将容器3000端口暴露出来,允许外部访问
EXPOSE 3000

# 起web服务
ENTRYPOINT ["npm", "run"]
CMD ["start"]
复制代码

3. 构建镜像

语法:docker build [OPTIONS] PATH | URL | -

# 构建一个镜像名为react-app、标签名为pro的镜像
docker build -t react-app:pro .
# -f 是指定文件名为dev.Dockerfile的Dockerfile来构建镜像。
docker build -t react-app:dev -f dev.Dockerfile .
复制代码

上面的命令有个.,这是个什么鬼???它指的是本次执行的上下文路径,可以指定Dockerfile 的绝对路径,由于docker的运行模式是C/S。我们本机是C,docker 引擎是 S。实际的构建过程是在docker引擎下完成的,所以这个时候无法用到我们本机的文件,比如我们想copy文件到镜像系统里。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。

3. 运行(创建)容器

命令:docker run -p 4000:3000 -it -d react-app:pro start。

其中几个参数的作用如下:

  • run:创建一个新的容器并运行一个命令,会返回创建的容器id;
  • -p:用来将容器内部端口暴露给外部端口的参数,其中4000是外部端口,3000是内部端口,所以上述应用访问地址为http://localhost:4000;
  • -it:容器的Shell映射到当前的Shell,然后你在本机窗口输入的命令,就会传入容器;
  • -d:在后台运行容器。如果你不想每个容器都占用一个终端窗口,这个参数会对你有帮助;
  • start:在镜像名称后面的参数是传入到容器内部的参数,这个参数是传给Dockerfile里的CMD,CMD命令有个默认值,如果不传这个参数会使用默认值运行命令。

如果你不想指定某个端口来访问容器内部端口,那么使用-P参数可以自动随机映射EXPOSE的端口:docker run -P -it -d react-app:pro,然后输入docker ps -a查看容器运行在了哪个端口上: docker-ps--a.png

4. 查看应用

这时候打开浏览器访问http://localhost:4000,可以看到运行在docker中的应用啦~

5. 发布image

把上述步骤生成的镜像发布到docker上,在hub.docker.com/上注册一个用户,登录:

docker login
复制代码

为本地镜像标注用户名和版本:

# docker image tag [imageName] [username]/[repository]:[tag]
docker image tag react-app:pro urnotzane/react-app:1.0.0
复制代码

发布镜像文件:

# docker image push [username]/[repository]:[tag]
docker image push urnotzane/react-app:1.0.0
复制代码

发布成功以后就可以在自己的用户页面看到自己发布的镜像了,本文镜像示例地址是urnotzane/react-app docker-push.png

分层结构

在Dockerfile中的每一行命令,都在Docker镜像层中以一个独立镜像层的形式存在,因此减少镜像层是一个优化镜像提及的好方法。镜像层最底层为最基础的系统镜像,然后是Dockerfile的命令形成的层,然后是容器的初始层,最后最顶层是容器所在的可读写层。如下图: construction.png

使用docker history [Image Name]可以查看历史镜像结构,拿本文所创建的镜像举例: docker-history.png 我们可以看到,Dockerfile里的每一个指令都会生成一个镜像,Docker的缓存就是缓存这些指令生成的镜像,所以一个Dockerfile文件里的指令越多,它生成的镜像文件就越大。

换个角度讲,如果Dockerfile的第N个指令的内容或指令涉及的文件内容发生了变更,那么后续的命令都会重新执行,而不会去取缓存的镜像,所以要想加速构建速度,可以从这个方面去优化。

Dockerfile解析

CMD

会被docker run的命令参数所覆盖,类似于RUN指令,不同之处是执行时间不同:

  • RUN是在docker build时运行;
  • CMD是在docker run时运行。

作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。如果Dockerfile存在多个CMD则只有最后一个会生效。

ENTRYPOINT

docker run的命令行参数会作为参数来传给ENTRYPOINT指令指定的程序,除非在docker run命令行使用了--entrypoint选项。 可搭配CMD使用,这里的CMD就是ENTRYPOINT的执行参数,比如:

...
ENTRYPOINT ["npm", "run"]
CMD ["start"]
复制代码

不传参运行docker run -P -it -d react-app:pro时是npm run start,传参如docker run -P -it -d react-app:pro build时,运行的则是npm run build

ENV

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

...
ENV NODE_ENV production
RUN echo ${NODE_ENV}
复制代码

ARG

构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有docker build的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

...
ARG USERNAME=''
ARG PASSWORD=''

RUN echo ${USERNAME} && echo ${PASSWORD}
...
复制代码

覆盖命令:docker build -t react-app:arg --build-arg USERNAME=zane --build-arg PASSWORD=123456 .

WORKDIR

WORKDIR指令设置Dockerfile中的任何RUN,CMD,ENTRPOINT,COPY和ADD指令的工作目录。如果WORKDIR指定的目录不存在,即使随后的指令没有用到这个目录,都会创建。

USER

用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。

COPY

复制指令,从上下文目录中复制文件或者目录到容器里指定路径。目录不存在时会自动创建。

优缺点

docker与虚拟机

  • 虚拟机如VMware之类需要模拟整台机器包括硬件环境,每台虚拟机都有完整的操作系统,一旦将资源预分配给虚拟机,那么这些资源将全部被占用;而docker容器不是模拟一个完整的操作系统,而是对进程进行隔离。(Hypervisor:虚拟机监视器(virtual machine monitor,缩写为 VMM),是用来创建与运行虚拟机的软件、固件或硬件。)。但是具体到底是怎么个隔离法还需深入研究,这里篇幅有限就先搁置。

docker-vm.png

  • Docker将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了Docker,就不用担心环境问题。
  • 容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

优点

  • 操作系统级别虚拟化,容器和内核交互,几乎无性能损耗。
  • 更轻量,各镜像共用一个内核与共享应用程序库,所占内存小。
  • 占用硬盘一般为MB,而虚拟机GB级别。
  • 高可用性,可快速重新部署。
  • 分钟级别虚拟化创建,秒级别容器创建。
  • Dockerfile记录容器构建过程,可在集群中实现快速分发和部署。
  • 持续集成性高,通过编辑Dockerfile文件自动拉取代码、编译构建、运行测试、结果记录、测试统计。

缺点

  • 隔离性较弱,Docker属于进程间的隔离,虚拟机属于系统级别的隔离。
  • 安全性较弱,一旦容器内的用户提升为root,它就直接具备了宿主机的root权限。

常用命令

  • 创建容器
    # 创建运行一个可交互式的容器
    docker run -it react-app:pro /bin/bash
    # 创建并后台运行一个可交互式的容器
    docker run -itd react-app:pro /bin/bash
    # 查看所有容器
    docker ps -a
    # 退出容器终端
    exit
    # 查看运行中的容器
    docker ps
    复制代码
  • 启动容器
    # 启动已停止的容器
    docker start <Container ID/NAME>
    # 重启容器
    docker restart <Container ID/NAME>
    复制代码
  • 停止容器
    # 停止运行中的容器
    docker stop <Container ID/NAME>
    复制代码
  • 进入容器
    # 退出容器会停止容器:docker attach [OPTIONS] CONTAINER
    docker attach <Container ID>
    # 退出容器不会停止容器
    docker exec -it <Container> /bin/bash
    复制代码
  • 删除容器
    # 删除指定容器
    docker rm -f <Container ID>
    # 清除所有终止状态的容器
    docker container prune
    复制代码
  • 运行web应用
    # 运行容器并暴露内部端口至随机端口
    docker run -itd -P react-app:pro
    # 运行容器并暴露内部端口至指定端口
    docker run -itd -p 32768:3000 react-app:pro
    # 查看内部输出日志
    docker logs -f <Container ID/NAME>
    # 查看内部运行进程
    docker top <Container ID>
    复制代码
  • 镜像清理 有时候由于调试代码产生很多的none的image,挨个清理会有些麻烦,下面是全部清理的命令:
    # 列出所有容器ID
    docker ps -aq
    # 停止所有容器
    docker stop $(docker ps -aq)
    docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker stop
    # 删除所有停止的容器
    docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker rm
    # 删除所有容器
    docker rmi $(docker ps -aq)
    # 删除所有tag标签是none的镜像
    docker images|grep none|awk '{print $3 }'|xargs docker rmi
    复制代码
  • 更新镜像
    docker run -it node:latest /bin/bash
    复制代码
    在运行的容器内使用apt-get update命令进行更新。

遇到的问题

  • docker run -p 4000:3000 -d react-app:pro start,其中-d是后台运行容器的意思,但是总是会直接退出,这是因为Docker在npm run start还没运行成功的时候检测到这个容器内没有运行中的前台应用,为了节省资源就给这个容器退出了(参考)。加个-it等待npm run start运行成功后,docker检查到你有运行一个前台应用,那么这个容器就不会被退出了。

参考

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

462

Relevant articles

未登录头像

No more data