docker/容器镜像.md

34 KiB
Raw Blame History

Docker核心概念

1、Docker系统

Docker系统有两个程序Docker服务端和Docker客户端

Docker服务端

是一个服务进程,管理着所有的容器。

docker engine

Docker客户端

扮演着docker服务端的远程控制器可以用来控制docker的服务端进程。

2、Docker核心组件

Docker 镜像 - Docker images

Docker 仓库 - Docker registeries

Docker 容器 - Docker containers

容器组成要素

名称空间 namespace

资源限制 cgroups

文件系统 overlay2(UnionFS)

2.1、Docker 仓库

用来保存镜像可以理解为代码控制中的代码仓库。同样的Docker 仓库也有公有和私有的概念。

公有的 Docker 仓库名字是 Docker Hub。Docker Hub提供了庞大的镜像集合供使用。这些镜像可以是自己创建或者在别人的镜像基础上创建。Docker 仓库是 Docker 的分发部分。

2.2、Docker 镜像

image-20200311140939156

Docker 镜像是 Docker 容器运行时的只读模板,每一个镜像由一系列的层 (layers) 组成。Docker 使用 UnionFS 来将这些层联合到单独的镜像中。UnionFS 允许独立文件系统中的文件和文件夹(称之为分支)被透明覆盖形成一个单独连贯的文件系统。正因为有了这些层的存在Docker 是如此的轻量。当你改变了一个 Docker 镜像,比如升级到某个程序到新的版本,一个新的层会被创建。因此,不用替换整个原先的镜像或者重新建立(在使用虚拟机的时候你可能会这么做),只是一个新的层被添加或升级了。现在你不用重新发布整个镜像,只需要升级,层使得分发 Docker 镜像变得简单和快速。

在 Docker 的术语里,一个只读层被称为镜像,一个镜像是永久不会变的。

由于 Docker 使用一个统一文件系统Docker 进程认为整个文件系统是以读写方式挂载的。 但是所有的变更都发生顶层的可写层,而下层的原始的只读镜像文件并未变化。由于镜像不可写,所以镜像是无状态的。

每一个镜像都可能依赖于由一个或多个下层的组成的另一个镜像。下层那个镜像是上层镜像的父镜像。

镜像名称组成

registry/repo:tag

基础镜像

一个没有任何父镜像的镜像,谓之基础镜像

镜像ID

所有镜像都是通过一个 64 位十六进制字符串 (内部是一个 256 bit 的值)来标识的。 为简化使用,前 12 个字符可以组成一个短ID可以在命令行中使用。短ID还是有一定的碰撞机率所以服务器总是返回长ID。

2.3、Docker 容器

Docker 容器和文件夹很类似一个Docker容器包含了所有的某个应用运行所需要的环境。每一个 Docker 容器都是从 Docker 镜像创建的。Docker 容器可以运行、开始、停止、移动和删除。每一个 Docker 容器都是独立和安全的应用平台Docker 容器是 Docker 的运行部分。

2.4、Docker镜像和容器区别

Docker镜像

要理解Docker镜像和docker容器之间的区别确实不容易。

假设Linux内核是第0层那么无论怎么运行Docker它都是运行于内核层之上的。这个Docker镜像是一个只读的镜像位于第1层它不能被修改或不能保存状态。

一个Docker镜像可以构建于另一个Docker镜像之上这种层叠关系可以是多层的。第1层的镜像层我们称之为基础镜像Base Image其他层的镜像除了最顶层我们称之为父层镜像Parent Image。这些镜像继承了他们的父层镜像的所有属性和设置并在Dockerfile中添加了自己的配置。

Docker镜像通过镜像ID进行识别。镜像ID是一个64字符的十六进制的字符串。但是当我们运行镜像时通常我们不会使用镜像ID来引用镜像而是使用镜像名来引用。

要列出本地所有有效的镜像,可以使用命令

[root@wxin.com ~]# docker images

镜像可以发布为不同的版本这种机制我们称之为标签Tag

如上图所示neo4j镜像有两个版本lastest版本和2.1.5版本。

可以使用pull命令加上指定的标签

[root@wxin.com ~]# docker pull ubuntu:14.04
[root@wxin.com ~]# docker pull ubuntu:12.04

Docker容器

Docker容器可以使用命令创建

[root@wxin.com ~]# docker run imagename

它会在所有的镜像层之上增加一个可写层。这个可写层有运行在CPU上的进程而且有两个不同的状态运行态Running和退出态 Exited。这就是Docker容器。当我们使用docker run启动容器Docker容器就进入运行态当我们停止Docker容器时它就进入退出态。

当我们有一个正在运行的Docker容器时从运行态到停止态我们对它所做的一切变更都会永久地写到容器的文件系统中。要切记对容器的变更是写入到容器的文件系统的而不是写入到Docker镜像中的。

我们可以用同一个镜像启动多个Docker容器这些容器启动后都是活动的彼此还是相互隔离的。我们对其中一个容器所做的变更只会局限于那个容器本身。

如果对容器的底层镜像进行修改,那么当前正在运行的容器是不受影响的,不会发生自动更新现象。

如果想更新容器到其镜像的新版本,那么必须当心,确保我们是以正确的方式构建了数据结构,否则我们可能会导致损失容器中所有数据的后果。

64字符的十六进制的字符串来定义容器ID它是容器的唯一标识符。容器之间的交互是依靠容器ID识别的由于容器ID的字符太长我们通常只需键入容器ID的前4个字符即可。当然我们还可以使用容器名但显然用4字符的容器ID更为简便。

3、名字空间

名字空间是 Linux 内核一个强大的特性。每个容器都有自己单独的名字空间,运行在其中的应用都像是在独立的操作系统中运行一样。名字空间保证了容器之间彼此互不影响。

  1. pid 名字空间

不同用户的进程就是通过 pid 名字空间隔离开的,且不同名字空间中可以有相同 pid。所有的 LXC 进程在 Docker中的父进程为Docker进程每个 LXC 进程具有不同的名字空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

  1. net 名字空间

有 了 pid 名字空间, 每个名字空间中的 pid 能够相互隔离,但是网络端口还是共享 host 的端口。网络隔离是通过 net 名字空间实现的,每个 net 名字空间有独立的 网络设备, IP 地址, 路由表, /proc/net 目录。这样每个容器的网络就能隔离开来。Docker 默认采用 veth 的方式,将容器中的虚拟网卡同 host 上的一 个Docker 网桥 docker0 连接在一起。

  1. ipc 名字空间

容器中进程交互还是采用了 Linux 常见的进程间交互方法(interprocess communication - IPC), 包括信号量、消息队列和共享内存、socket、管道等。然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 名字空间中的进程间交互,因此需要在 IPC 资源申请时加入名字空间信息,每个 IPC 资源有一个唯一的 32 位 id。

  1. mnt名字空间

类似 change root将一个进程放到一个特定的目录执行。mnt 名字空间允许不同名字空间的进程看到的文件结构不同,这样每个名字空间 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个名字空间中的容器在 /proc/mounts 的信息只包含所在名字空间的 mount point。

  1. uts 名字空间

UTS("UNIX Time-sharing System") 名字空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非主机上的一个进程。

  1. user 名字空间

每个容器可以有不同的用户和组 id, 也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户。

镜像管理

搜索镜像

这种方法只能用于官方镜像库,例如搜索基于 Centos 操作系统的镜像

[root@wxin.com ~]# docker search centos

按星级搜索镜像,例如查找 star 数至少为 100 的镜像,默认不加 s 选项找出所有相关 ubuntu 镜像

[root@wxin.com ~]# docker search ubuntu -f stars=100  

拉取镜像

[root@wxin.com ~]# docker pull centos

查看本地镜像

[root@wxin.com ~]# docker image list

查看镜像详情

[root@wxin.com ~]# docker image inspect 镜像id

只查看所有镜像的id

[root@wxin.com ~]# docker images -q 

查看镜像制作的过程相当于Dockerfile

[root@wxin.com ~]# docker history daocloud.io/ubuntu

删除镜像 删除一个或多个多个之间用空格隔开可以使用镜像名称或id

[root@wxin.com ~]# docker rmi daocloud.io/library/mysql

强制删除 如果镜像正在被使用,可用--force强制删除

[root@wxin.com ~]# docker rmi docker.io/ubuntu:latest --force

删除所有镜像

[root@wxin.com ~]# docker rmi $(docker images -q)

给镜像打tag

[root@wxin.com ~]# docker tag daocloud.io/ubuntu daocloud.io/ubuntu:v1

容器管理

创建新容器但不启动

[root@wxin.com ~]# docker create -it daocloud.io/library/centos:5 /bin/bash

创建并运行一个新容器 同一个镜像可以启动多个容器,每次执行run子命令都会运行一个全新的容器

[root@wxin.com ~]# docker run -itd --name="test" --restart=always centos /bin/bash

-i 捕获标准输入输出 -t 分配一个终端或控制台 -d 后台运行 --restart=always 容器随docker engine自启动因为重启docker时默认容器都会被关闭,也适用于create子命令 --rm 当你仅仅需要短暂的运行一个容器且数据不需保存你可能就希望Docker能在容器结束时自动清理其所产生的数据。这时就需要--rm参数了。注意--rm 和 -d不能共用

--name="容器名称" 为容器分配一个名字如果没有指定docker会自动分配一个随机名称 可以通过三种方式调用容器: 1使用UUID长命名"f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778" 2使用UUID短Id"f78375b1c487" 3使用Name("evil_ptolemy")

UUID标识是由Docker deamon生成的。 如果执行docker run时没指定--name那么deamon会自动生成一个随机字符串。 但是对于一个容器来说有个name会非常方便当需要连接其它容器时或者需要区分其它容器时使用容器名可以简化操作。无论容器运行在前台或者后台这个名字都是有效的。

保存容器PID 如果在使用Docker时有自动化的需求你可以将containerID输出到指定的文件中PIDfile类似于某些应用程序将自身ID输出到文件中方便后续脚本操作。 --cidfile="文件名"

断开与容器的连接且关闭容器

root@d33c4e8c51f8 /#exit
快捷键ctrl+d
如果只想断开和容器的连接而不关闭容器可使用快捷键ctrl+p+q

ps 查看容器

[root@wxin.com ~]#docker ps  //只查看运行状态的容器

-a 查看所有容器

[root@wxin.com ~]#docker ps -a

-q 只查看容器id

[root@wxin.com ~]# docker ps -a -q

-l 列出最近一次启动的容器(了解)

[root@wxin.com ~]# docker ps -l  

inspect 查看容器详细信息

用于查看容器的配置信息,包含容器名、环境变量、运行命令、主机配置、网络配置和数据卷配置等。

[root@wxin.com ~]# docker inspect d95 //d95是我机器上运行的一个容器ID的前3个字符

比如容器里在安装ip或ifconfig命令之前查看网卡IP显示容器IP地址和端口号如果输出是空的说明没有配置IP地址不同的Docker容器可以通过此IP地址互相访问

[root@wxin.com ~]# docker inspect --format='{{.NetworkSettings.IPAddress}}'  容器id

关于inspect的用法有很多如果想多了解可向大强哥索取文档**"扩展-Docker 查看容器信息-格式化.md"**

start 启动容器

[root@wxin.com ~]# docker start  name 

stop kill 关闭容器

[root@wxin.com ~]# docker stop  name    //正常关闭
[root@wxin.com ~]# docker kill  name    //强制关闭容器

关闭所有running状态的容器
[root@wxin.com ~]# docker kill $(docker ps -q)  

rm 删除容器

[root@wxin.com ~]# docker rm 容器id或名称
要删除一个运行中的容器,添加 -f 参数

根据状态删除所有容器
[root@wxin.com ~]# docker rm $(docker ps -qf status=exited)

restart 重启容器

[root@wxin.com ~]# docker restart name

pause 暂停容器

[root@wxin.com ~]# docker pause name

unpause 恢复容器与pause相对应

[root@wxin.com ~]# docker unpause name

rename 修改容器名称

[root@wxin.com ~]# docker rename mytest test

stats 动态显示容器的资源消耗情况包括CPU、内存、网络I/O

[root@wxin.com ~]# docker rename name

port 输出容器端口与宿主机端口的映射情况

[root@wxin.com ~]# docker port blog
80/tcp -> 0.0.0.0:80
宿主机的80端口映射到容器blog的内部端口80这样可通过宿主机的80端口查看容器blog提供的服务

连接容器 方法1、attach

[root@wxin.com ~]# docker attach 容器id  
前提是容器创建时必须指定了交互shell

方法2、exec 通过exec命令可以创建两种任务后台型任务和交互型任务 交互型任务

[root@wxin.com ~]# docker exec -it  容器id  /bin/bash
root@68656158eb8e:/ ls    

后台型任务

[root@wxin.com ~]# docker exec 容器id touch /testfile

监控容器运行 可以使用logs、top、events、wait这些子命令 logs 可以通过使用docker logs命令来查看容器的运行日志其中--tail选项可以指定查看最后几条日志而-t选项则可以对日志条目附加时间戳。使用-f选项可以跟踪日志的输出直到手动停止。

[root@wxin.com ~]# docker logs   App_Container  //不同终端操作
[root@wxin.com ~]# docker logs -f App_Container
[root@wxin.com ~]# docker run -itd --name mytest docker.io/centos /bin/sh -c "while true; do echo hello world; sleep 2; done"
37738fe3d6f9ef26152cb25018df9528a89e7a07355493020e72f147a291cd17
[root@wxin.com ~]# docker logs mytest
hello world
hello world

docker run -itd --name mytest 298ec /bin/sh -c "while true; do echo hello world; sleep 2; done"

top 显示一个运行的容器里面的进程信息

[root@wxin.com ~]# docker top birdben/ubuntu:v1

events Get real time events from the server 实时输出Docker服务器端的事件包括容器的创建启动关闭等。

[root@wxin.com ~]# docker start loving_meninsky
    	loving_meninsky
[root@wxin.com ~]# docker events  //不同终端操作
2017-07-08T16:39:23.177664994+08:00 network connect 
df15746d60ffaad2d15db0854a696d6e49fdfcedc7cfd8504a8aac51a43de6d4 (container=50a0449d7729f94046baf0fe5a1ce2119742261bb3ce8c3c98f35c80458e3e7a, 
name=bridge, type=bridge)
2017-07-08T16:39:23.356162529+08:00 container start 
50a0449d7729f94046baf0fe5a1ce2119742261bb3ce8c3c98f35c80458e3e7a (image=ubuntu, name=loving_meninsky)

waitX Block until a container stops, then print its exit code --捕捉容器停止时的退出码 执行此命令后,该命令会"hang"在当前终端,直到容器停止,此时,会打印出容器的退出码

[root@wxin.com ~]# docker wait 01d8aa  //不同终端操作
137

diff 查看容器内发生改变的文件以elated_lovelace容器为例

[root@68656158eb8e:/]# touch c.txt
用diff查看
包括文件的创建、删除和文件内容的改变都能看到 
[root@wxin.com ~]# docker diff  容器名称
A /c.txt

C对应的文件内容的改变A对应的均是文件或者目录的创建删除
[root@wxin.com ~]# docker diff 7287
    A /a.txt
    C /etc
    C /etc/passwd
    A /run
    A /run/secrets   

宿主机和容器之间相互Copy文件 cp的用法如下 docker cp [OPTIONS] CONTAINER:PATH LOCALPATH docker cp [OPTIONS] LOCALPATH CONTAINER:PATH 如容器mysql中/usr/local/bin/存在docker-entrypoint.sh文件可如下方式copy到宿主机

[root@wxin.com ~]# docker cp mysql:/usr/local/bin/docker-entrypoint.sh  /root

修改完毕后将该文件重新copy回容器

[root@wxin.com ~]# docker cp /root/docker-entrypoint.sh mysql:/usr/local/bin/    

容器镜像制作

1、容器文件系统打包

将容器的文件系统打包成tar文件,也就是把正在运行的容器直接导出为tar包的镜像文件

export Export a container's filesystem as a tar archive

有两种方式elated_lovelace为容器名

第一种:
  [root@wxin.com ~]# docker export -o elated_lovelace.tar elated_lovelace
第二种:
  [root@wxin.com ~]# docker export 容器名称 > 镜像.tar

导入镜像归档文件到其他宿主机

import Import the contents from a tarball to create a filesystem image

  [root@wxin.com ~]# docker import elated_lovelace.tar  elated_lovelace:v1

注意:

如果导入镜像时没有起名字,随后可以单独起名字(没有名字和tag)可以手动加tag

 [root@wxin.com ~]# docker  tag  镜像ID  mycentos:7

2、通过容器创建本地镜像

背景:

容器运行起来后,又在里面做了一些操作,并且要把操作结果保存到镜像里

方案:

使用 docker commit 指令把一个正在运行的容器直接提交为一个镜像。commit 是提交的意思,类似告诉svn服务器我要生成一个新的版本。

例:在容器内部新建了一个文件

[root@wxin.com ~]# docker exec -it 4ddf4638572d /bin/sh  
[root@4ddf4638572d \]# touch test.txt
[root@4ddf4638572d \]# exit
将这个新建的文件提交到镜像中保存
[root@wxin.com ~]# docker commit 4ddf4638572d wing/helloworld:v2

例:

[root@wxin.com ~]# docker commit -m "my images version1" -a "wing" 108a85b1ed99 daocloud.io/ubuntu:v2
  sha256:ffa8a185ee526a9b0d8772740231448a25855031f25c61c1b63077220469b057
  -m                    添加注释
  -a                    作者author
  108a85b1ed99          容器环境id
  daocloud.io/ubuntu:v2    镜像名称hub的名称/镜像名称tag 
  -ppause=true        提交时暂停容器运行

Init 层的存在,是为了避免执行 docker commit 时,把 Docker 自己对 /etc/hosts 等文件做的修改,也一起提交掉。

3、镜像迁移

保存一台宿主机上的镜像为tar文件然后可以导入到其他的宿主机上

save Save an image(s) to a tar archive

将镜像打包与下面的load命令相对应

[root@wxin.com ~]# docker save -o nginx.tar nginx
[root@wxin.com ~]# docker save > nginx.tar nginx

load Load an image from a tar archive or STDIN

与上面的save命令相对应将上面sava命令打包的镜像通过load命令导入

[root@wxin.com ~]# docker load < nginx.tar

注:

1.tar文件的名称和保存的镜像名称没有关系

2.导入的镜像如果没有名称自己打tag起名字

4、通过Dockerfile创建镜像

虽然可以自己制作 rootfs(见'容器文件系统那些事儿')但Docker 提供了一种更便捷的方式,叫作 Dockerfile

docker build命令用于根据给定的Dockerfile和上下文以构建Docker镜像。

docker build语法

[root@wxin.com ~]# docker build [OPTIONS] <PATH | URL | ->  

选项说明

--build-arg设置构建时的变量

--no-cache默认false。设置该选项将不使用Build Cache构建镜像

--pull默认false。设置该选项总是尝试pull镜像的最新版本

--compress默认false。设置该选项将使用gzip压缩构建的上下文

--disable-content-trust默认true。设置该选项将对镜像进行验证

--file, -fDockerfile的完整路径默认值为PATH/Dockerfile

--isolation默认--isolation="default"即Linux命名空间其他还有process或hyperv

--label为生成的镜像设置metadata

--squash默认false。设置该选项将新构建出的多个层压缩为一个新层但是将无法在多个镜像之间共享新层设置该选项实际上是创建了新image同时保留原有image。

--tag, -t镜像的名字及tag通常name:tag或者name格式可以在一次构建中为一个镜像设置多个tag

--network默认default。设置该选项Set the networking mode for the RUN instructions during build

--quiet, -q 默认false。设置该选项Suppress the build output and print image ID on success

--force-rm默认false。设置该选项总是删除掉中间环节的容器

--rm默认--rm=true即整个构建过程成功后删除中间环节的容器

PATH | URL | -说明

给出命令执行的上下文。

docker build需要的各种文件必须放到上下文目录中

上下文可以是构建执行所在的本地路径也可以是远程URL如Git库、tar包或文本文件等。

如果是Git库https://github.com/docker/rootfs.git#container:docker则隐含先执行git clone --depth 1 --recursive到本地临时目录然后再将该临时目录发送给构建进程。

构建镜像的进程中可以通过ADD命令将上下文中的任何文件加入到镜像中。

-表示通过STDIN给出Dockerfile或上下文。

示例:

  docker build - < Dockerfile 

说明该构建过程只有Dockerfile没有上下文

 docker build - < context.tar.gz

说明其中Dockerfile位于context.tar.gz的根路径

可以同时设置多个tag

[root@wxin.com ~]# docker build -t champagne/bbauto:latest -t champagne/bbauto:v2.1 . 

创建镜像所在的文件夹和Dockerfile文件

mkdir dockerfile-test 
cd dockerfile-test 
touch Dockerfile 

在Dockerfile文件中写入指令每一条指令都会更新镜像的信息例如

[root@wxin.com ~]# vim Dockerfile 
# This is a comment 
FROM  daocloud.io/library/centos:7 
MAINTAINER wing wing@wxin.com
RUN  命令1;命令2命令3
RUN  命令

格式说明

每行命令都是以 INSTRUCTION statement 形式,就是命令+ 清单的模式。命令要大写,"#"是注解。

FROM 命令是告诉docker 我们的镜像什么。

MAINTAINER 是描述 镜像的创建人。

RUN 命令是在镜像内部执行。就是说他后面的命令应该是针对镜像可以运行的命令。

创建镜像

[root@wxin.com ~]# docker build  -t  centos:v2 . 
build  是docker创建镜像的命令 
-t  镜像名称  
"." 上下文目录,如果不用-f指定Dockerfile文件的位置和名称默认会从上下文目录也就是这里指定的当前目录查找 

创建完成后,用这个镜像运行容器

[root@wxin.com ~]# docker run -it centos:v2 /bin/bash

--build-arg应用示例

# cat Dockerfile 
FROM daocloud.io/library/nginx:1.7.9
MAINTAINER wing wing@qq.com
ARG TEST
ARG TEST1
RUN echo $TEST > /a.txt
RUN echo $TEST1 > /b.txt

# docker build --build-arg TEST=123 --build-arg TEST1=456 -t nginx:v11 . 

5、例01-容器化Python的Flask应用

目标

用 Docker 部署一个用 Python 编写的 Web 应用。

私有镜像仓库 OP

下载python RD

运行成容器 RD

写代码 RD

打包tar RD

上传tar包到私有仓库 RD

拉取带应用的镜像到测试服 OP

发版上线 OP

应用代码部分

代码功能:如果当前环境中有"NAME"这个环境变量,就把它打印在"Hello"后,否则就打印"Hello world",最后再打印出当前环境的 hostname。

[root@wxin.com ~]# mkdir python_app
[root@wxin.com ~]# cd python_app
[root@wxin.com ~]# vim app.py
from flask import Flask
import socket
import os
app = Flask(__name__)
@app.route('/') 
def hello():
  html = "<h3>Hello {name}!</h3><b>Hostname:</b> {hostname}<br/>"      
  return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
if __name__ == "__main__":
  app.run(host='0.0.0.0', port=80)

代码测试

# yum search pip

# yum install python2-pip
或者
# yum install python3-pip

# pip3 install flask

# python3 app.py

应用依赖

定义在同目录下的 requirements.txt 文件里,内容如下:

[root@wxin.com ~]# vim requirements.txt
Flask

编写Dockerfile

[root@wxin.com ~]# vim Dockerfile
FROM daocloud.io/library/python:3
WORKDIR /app
ADD  .  /app
#RUN pip install --trusted-host pypi.python.org -r requirements.txt
RUN pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
EXPOSE 80
ENV NAME World
# ENTRYPOINT ['/bin/sh','-c'] #新版本20.10.17的ENTRYPOINT有问题可能会报找不到命令错误
CMD ["python", "app.py"]

image-20200722153133293

Dockerfile文件说明

FROM python:2.7-slim

使用官方提供的 Python 开发镜像作为基础镜像

指定"python:2.7-slim"这个官方维护的基础镜像,从而免去安装 Python 等语言环境的操作。否则,这一段就得这么写了:

FROM ubuntu:latest

RUN apt-get update -y

RUN apt-get install -y python-pip python-dev build-essential

WORKDIR /app

将工作目录切换为 /app意思是在这一句之后Dockerfile 后面的操作都以这一句指定的 /app 目录作为当前目录。

ADD . /app

将当前目录下的所有内容复制到 /app 下Dockerfile 里的原语并不都是指对容器内部的操作。比如 ADD指的是把当前目录即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。

RUN pip install --trusted-host pypi.python.org -r requirements.txt

使用 pip 命令安装这个应用所需要的依赖

EXPOSE 80 //允许外界访问容器的 80 端口

ENV NAME World //设置环境变量

CMD ["python", "app.py"]

设置容器进程为python app.py这个 Python 应用的启动命令

这里app.py 的实际路径是 /app/app.py。CMD ["python", "app.py"] 等价于 "docker run python app.py"。

在使用 Dockerfile 时,可能还会看到一个叫作 ENTRYPOINT 的原语。它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:"ENTRYPOINT CMD"。

但是默认Docker 会提供一个隐含的 ENTRYPOINT/bin/sh -c。所以在不指定 ENTRYPOINT 时,比如在这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c "python app.py",即 CMD 的内容就是 ENTRYPOINT 的参数。

基于以上原因,后面会统一称 Docker 容器的启动进程为 ENTRYPOINT而不是 CMD。

现在目录结构:

[root@wxin.com ~]# ls
Dockerfile  app.py  requirements.txt

构建镜像

[root@wxin.com ~]# docker build -t helloworld .
-t  给这个镜像加一个 Tag

Dockerfile 中的每个原语执行后都会生成一个对应的镜像层。即使原语本身并没有明显地修改文件的操作比如ENV 原语),它对应的层也会存在。只不过在外界看来,这个层是空的。

查看结果

[root@wxin.com ~]# docker image ls
REPOSITORY       TAG         	 IMAGE ID
helloworld     	 latest          653287cdf998

启动容器

[root@wxin.com ~]# docker run -p 4000:80 helloworld 
镜像名 helloworld 后面,什么都不用写,因为在 Dockerfile 中已经指定了 CMD。
否则,就得把进程的启动命令加在后面:
[root@wxin.com ~]# docker run -p 4000:80 helloworld python app.py

查看容器

[root@wxin.com ~]# docker ps
CONTAINER ID     IMAGE         COMMAND       		 CREATED
4ddf4638572d     helloworld    "python app.py"   10 seconds ago

进入容器

[root@wxin.com ~]# docker exec -it b69 /bin/bash

访问容器内应用

[root@wxin.com ~]# curl http://localhost:4000

Hello World!

Hostname: 4ddf4638572d

至此,已经使用容器完成了一个应用的开发与测试,如果现在想要把这个容器的镜像上传到 DockerHub 上分享给更多的人,首先要注册一个 Docker Hub 账号,然后使用 docker login 命令登录。

给容器镜像打tag起一个完整的名字

[root@wxin.com ~]# docker tag helloworld yanqiang20072008/helloworld:v1
yanqiang20072008为我在docker hub的用户名

推送镜像到Docker Hub

[root@wxin.com ~]# docker push yanqiang20072008/helloworld:v1

6、例02-部署docker官方文档

# docker run -d -p 8888:80 docker/getting-started

7、例03-部署node.js网站

注意:构建镜像的过程非常缓慢,建议有时间的时候再尝试此实验

访问上例中部署的网站下载app.zip压缩包

image-20220719001900850

文件已经下载到wing docker文档目录

在解压后的app目录中创建Dockerfile文件内容如下

# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

构建镜像

注意:构建镜像的过程非常缓慢,建议有时间的时候再尝试此实验
# docker build -t getting-started .

启动容器

# docker run -dp 3000:3000 getting-started

访问应用

浏览器http://localhost:3000

image-20210512150612818

Update the source code

In the src/static/js/app.js file, update line 56 to use the new empty text.

去掉此行<p className="text-center">No items yet! Add one above!</p>
添加本行<p className="text-center">You have no todo items yet! Add one above!</p>

Lets build our updated version of the image, using the same command we used before.

 # docker build -t getting-started .

Lets start a new container using the updated code.

 # docker run -dp 3000:3000 getting-started

查看结果

image-20210512152020632

8、例04-制作Kubectl的镜像

本实例学完k8s之后才能做

制作镜像wing/kubectl

[root@wxin.com ~]#cat Dockerfile/kubectl/Dockerfile
FROM alpine
MAINTAINER wing <276267003@qq.com>
LABEL org.label-schema.vcs-ref=$VCS_REF \
   org.label-schema.vcs-url="https://github.com/vfarcic/kubectl" \
   org.label-schema.docker.dockerfile="/Dockerfile"
ENV KUBE_LATEST_VERSION="v1.13.0"
RUN apk add --update ca-certificates && \
  apk add --update -t deps curl && \
  curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl && \
  chmod +x /usr/local/bin/kubectl && \
  apk del --purge deps && \
  rm /var/cache/apk/* 
CMD ["kubectl", "help"]

9、Dockerfile优化

控制镜像大小

编译一个简单的nginx成功以后发现好几百M。

  1. 选择合适的基础镜像: 选择一个轻量级、经过优化的基础镜像作为起点。官方提供的基础镜像通常非常小且已经经过优化,如 Alpine、Scratch 等。
  2. 最小化安装软件包: 只安装应用程序所需的最小化软件包,避免不必要的依赖。删除不需要的软件包、文件和文档,以减小镜像大小。
  3. 使用多阶段构建: 使用多阶段构建可以减小最终镜像的大小。在构建过程中,可以使用一个镜像作为构建环境,然后从中提取构建好的结果并将其复制到一个较小的镜像中。
  4. 使用 .dockerignore 文件: 使用 .dockerignore 文件来排除不需要复制到镜像中的文件和目录,以减少构建上下文的大小,从而减小镜像大小。
  5. 减少镜像层数: 减少镜像的层数可以减小镜像的大小。将多个操作合并为一个步骤,避免不必要的层。 RUN 命令要尽量写在一条里,每次 RUN 命令都是在之前的镜像上封装,只会增大不会减小
  6. 清理临时文件和缓存: 构建过程中产生的临时文件和缓存可能会增加镜像大小在构建完成后进行清理。每次进行依赖安装后使用yum clean all清除缓存中的rpm头文件和包文件

使用构建缓存

Docker使用构建缓存可以显著加快Docker镜像的构建速度。当你在Dockerfile中执行指令时Docker会检查它之前的指令是否已经执行过并且是否存在缓存如果存在缓存Docker将重用缓存而不是重新执行指令。

以下是几个在Dockerfile中使用构建缓存的常用技巧

  1. 按顺序列出指令。将常用但不容易变更的指令,如FROMLABELENV放在Dockerfile的顶部将变更频繁的指令ADDCOPYRUN放在Dockerfile的底部。这样可以最大程度地利用缓存减少不必要的重新构建。
  2. 限制缓存范围。可以使用COPY指令的--chown参数或RUN指令的--mount参数来限制缓存的范围。例如,COPY --chown=root:root file.txt /tmp将限制缓存仅适用于该指令所复制的文件,而不适用于其他文件或目录。这样可以避免由于其他文件或目录的更改而不必要地重新构建。
  3. 避免更新不必要的依赖项。在使用RUN指令安装依赖项时,可以指定版本号,以避免不必要的更新。例如,RUN apt-get update && apt-get install -y python3=3.8.2-1ubuntu1将只安装指定版本的Python3而不是最新版本从而节省构建时间。
  4. 在需要清除缓存时使用--no-cache选项。有时,缓存可能会导致错误或意外的结果。在这种情况下,可以使用docker build --no-cache命令来禁用缓存并从头开始重新构建Docker镜像。

以上是一些常见的使用构建缓存的技巧可以帮助你优化Docker镜像的构建速度提高效率。