docker

第1章 容器生态系统

01 了解容器技术
  1. 系统讲解当前最流行的容器技术。 从容器的整个生态环境到各个具体的技术,从整体到细节逐一讨论。

  2. 重实践并兼顾理论。 从实际操作的角度带领大家学习容器技术。

为什么要写这个?

简单回答是:容器技术非常热门,但门槛高。 容器技术是继大数据和云计算之后又一炙手可热的技术,而且未来相当一段时间内都会非常流行。 对 IT 行业来说,这是一项非常有价值的技术。而对 IT 从业者来说,掌握容器技术是市场的需要,也是提 升自我价值的重要途径。 现在以 Docker 为代表的容器技术来了,而且关注度越来越高,这一点可以从 google 趋势 中 Docker 的 搜索上升趋势(蓝色曲线)中清楚看到。

在这里插入图片描述

02安装docker
环境选择

容器需要管理工具、runtime 和操作系统,我们的选择如下:

1.管理工具 - Docker Engine,Docker 最流行使用最广泛。

2.runtime - runc,Docker 的默认 runtime

3.操作系统 - CentOS Stream8

安装操作系统

基于 CentOS-Stream-8模板制作.pdf 这个实验手册做出来的模板克隆一个虚拟机命名为docker,并配置
静态IP 192.168.108.30,主机名改为docker

在这里插入图片描述

[root@localhost ~]# hostnamectl set-hostname docker
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual
ipv4.addresses 192.168.108.30/24 ipv4.gateway 192.168.108.2 ipv4.dns
192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160
安装 Docker

Docker 支持几乎所有的 Linux 发行版,也支持 Mac 和 Windows。各操作系统的安装方法可以访问:htt
https://docs.docker.com/engine/installation/

卸载旧版本(可选)
[root@docker ~]# yum remove docker-ce
安装必要工具
# devicemapper 存储驱动已经在 docker 18.09 版本中被废弃,所以在后续的安Docker装中无需安装
devicemapper支持。
[root@localhost ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
vim
[root@localhost ~]# yum-config-manager --add-repo
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/dockerce.repo
[root@localhost ~]# yum makecache
Docker CE Stable - x86_64
57 kB/s | 66 kB 00:01
CentOS Stream 8 - BaseOS
8.3 kB/s | 3.9 kB 00:00
CentOS Stream 8 - AppStream
9.8 kB/s | 4.4 kB 00:00
Metadata cache created.
allinone部署
安装软件
[root@docker ~]# yum install -y docker-ce
配置服务
[root@docker ~]# systemctl enable docker.service --now
验证安装
查看docker版本
[root@docker ~]# docker --version
Docker version 26.1.3, build b72abbb
验证docker状态
[root@docker ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor
preset: disabled)
Active: active (running) since Wed 2025-09-03 13:51:07 CST; 36s ago
Docs: https://docs.docker.com
Main PID: 1813 (dockerd)
Tasks: 10
Memory: 135.7M
CGroup: /system.slice/docker.service
└─1813 /usr/bin/dockerd -H fd:// --
containerd=/run/containerd/containerd.sock
配置镜像加速器(华为云)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

[root@docker ~]# vi /etc/docker/daemon.json
#加速器地址
# 按“Esc”,输入:wq保存并退出。
#重启容器引擎
[root@docker ~]# systemctl restart docker
#确认配置结果
[root@docker ~]# docker info
Client: Docker Engine - Community
Version: 26.1.3
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.14.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.27.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 26.1.3
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
runc version: v1.1.12-0-g51d5e94
init version: de40ad0
Security Options:
seccomp
Profile: builtin
Kernel Version: 4.18.0-553.6.1.el8.x86_64
Operating System: CentOS Stream 8
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 7.486GiB
Name: docker
ID: 96160847-9a97-40f4-997c-d12efaaef738
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
`https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/ <---
看这
Live Restore Enabled: false
运行第一个容器

环境就绪,马上运行第一个容器,执行命令:

[root@docker ~]# docker run hello-world

其过程可以简单的描述为:

  1. 从本地查找hello-wrold镜像,没找到
  2. 从 Docker Hub 下载hello-world镜像。
  3. 启动hello-world容器。
    清空刚才的实验环境:
[root@docker ~]# docker rm -f $(docker ps -aq) #删除所有容器
[root@docker ~]# docker rmi -f hello-world #删除镜像hello-world

此刻docker环境没有问题了,关机拍摄快照

运行第二个容器
[root@docker ~]# docker run -d -p 80:80 httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
852e50cd189d: Pull complete
67d51c33d390: Pull complete
b0ad2a3b9567: Pull complete
136f1f71f30c: Pull complete
01f8ace29294: Pull complete
Digest: sha256:fddc534b7f6bb6197855be559244adb11907d569aae1283db8e6ce8bb8f6f456
Status: Downloaded newer image for httpd:latest
817f24cca3514568f9b7a7cac6f183734077fb74caf4c62502acd2a4d0d29520

其过程可以简单的描述为:

  1. 从 Docker Hub 下载 httpd 镜像。镜像中已经安装好了 Apache HTTP Server。
  2. 启动 httpd 容器,并将容器的 80 端口映射到 host 的 80 端口。
    下面我们可以通过浏览器验证容器是否正常工作。在浏览器中输入 http://[your host os IP]
[root@docker ~]# curl 192.168.108.30
<html><body><h1>It works!</h1></body></html>
03 docker C/S分离部署

基于 CentOS-Stream-8模板制作.pdf 这个实验手册做出来的模板克隆虚拟机命名为docker_client和docker_server

docker server端配置
配置ip 192.168.108.30
配置hostname docker_server

[root@localhost ~]# hostnamectl set-hostname docker_server
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual
ipv4.addresses 192.168.108.30/24 ipv4.gateway 192.168.108.2 ipv4.dns
192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160

安装软件

[root@docker_server ~]# yum install -y yum-utils device-mapper-persistent-data
lvm2 vim
[root@localhost ~]# yum-config-manager --add-repo
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/dockerce.repo
[root@docker_server ~]# yum makecache
[root@docker_server ~]# yum install -y docker-ce
[root@docker_server ~]# systemctl enable docker.service --now
[root@docker_server ~]# vi /etc/docker/daemon.json
{
"registry-mirrors": [
"https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com" ]
}
# 按“Esc”,输入:wq保存并退出。
#重启容器引擎
[root@docker_server ~]# systemctl restart docker

配置服务

[root@docker_server ~]# vim /usr/lib/systemd/system/docker.service
# 在ExecStart参数中最后添加 -H tcp://0.0.0.0:2375,docker默认监听2375
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
-H tcp://0.0.0.0:2375
[root@docker_server ~]# systemctl daemon-reload
[root@docker_server ~]# systemctl restart docker.service
[root@docker_server ~]# systemctl stop firewalld

验证

[root@docker_server ~]# yum install lsof
[root@docker_server ~]# lsof -i :2375
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
dockerd 5440 root 3u IPv6 44991 0t0 TCP *:docker (LISTEN)

docker_client端
配置ip 192.168.108.31

[root@localhost ~]# hostnamectl set-hostname docker_client
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual
ipv4.addresses 192.168.108.31/24 ipv4.gateway 192.168.108.2 ipv4.dns
192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160

只安装docker客户端

[root@docker-client ~]# yum install -y yum-utils device-mapper-persistent-data
lvm2 vim
[root@docker-client ~]# yum-config-manager --add-repo
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/dockerce.repo
[root@docker-client ~]# yum makecache
[root@docker-client ~]# yum install -y docker-ce-cli

验证

[root@docker_client ~]# docker run hello-world #client直接执行报错,没有装服
务端
docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is
the docker daemon running?.
See 'docker run --help'.
# client端连接server端执行命令
[root@docker-client ~]# docker -H 192.168.108.30 run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
[root@docker-client ~]# docker -H 192.168.108.30 images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d2c94e258dcb 17 months ago 13.3kB
#这里也可以切换到Server端查看现象

说明:client只做管理,image和container存储在server端。

第2章 容器架构

04 容器What,Why,How

What - 什么是容器?
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。
容器与虚拟机

谈到容器,就不得不将它与虚拟机进行对比,因为两者都是为应用提供封装和隔离。
容器由两部分组成:

1.应用程序本身

2.依赖:比如应用程序需要的库或其他软件

容器在 Host 操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于的虚拟机。
传统的虚拟化技术,比如 VMWare, KVM, Xen,目标是创建完整的虚拟机。为了运行应用,除了部署应
用本身及其依赖(通常几十 MB),还得安装整个操作系统(几十 GB)。
下图展示了二者的区别。

在这里插入图片描述

如图所示,由于所有的容器共享同一个 Host OS,这使得容器在体积上要比虚拟机小很多。另外,启动容器不需要启动整个操作系统,所以容器部署和启动速度更快,开销更小,也更容易迁移。
Why - 为什么需要容器?
为什么需要容器?容器到底解决的是什么问题?
简要的答案是:容器使软件具备了超强的可移植能力。
容器解决的问题
我们来看看今天的软件开发面临着怎样的挑战?
如今的系统在架构上较十年前已经变得非常复杂了。以前几乎所有的应用都采用三层架构
(Presentation/Application/Data),系统部署到有限的几台物理服务器上(Web Server/Application
Server/Database Server)。
而今天,开发人员通常使用多种服务(比如 MQ,Cache,DB)构建和组装应用,而且应用很可能会部
署到不同的环境,比如虚拟服务器,私有云和公有云。

容器的优势

对于开发人员 - Build Once, Run Anywhere

容器意味着环境隔离和可重复性。开发人员只需为应用创建一次运行环境,然后打包成容器便可在其他机器上运行。另外,容器环境与所在的 Host 环境是隔离的,就像虚拟机一样,但更快更简单。

对于运维人员 - Configure Once, Run Anything

只需要配置好标准的 runtime 环境,服务器就可以运行任何容器。这使得运维人员的工作变得更高效,

一致和可重复。容器消除了开发、测试、生产环境的不一致性。

How - 容器是如何工作的?

从下节开始我们将学习容器核心知识的最主要部分。首先会介绍 Docker 的架构,然后分章节详细讨论

Docker 的镜像、容器、网络和存储。

容器与虚拟机比较

谈到容器,就不得不将它与虚拟机进行对比,因为两者都是为应用提供封装和隔离。

容器由两部分组成:

  1. 应用程序本身
  2. 依赖:比如应用程序需要的库或其他软件

容器在 Host 操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于的虚拟机。

传统的虚拟化技术,比如 VMWare, KVM, Xen,目标是创建完整的虚拟机。为了运行应用,除了部署应

用本身及其依赖(通常几十 MB),还得安装整个操作系统(几十 GB)。

05Docker架构详解

完整的Docker由以下几部分构成:
(1) 守护进程(Daemon):Docker守护进程(dockerd)侦听Docker API请求并管理Docker对象,,如图
像、容器、网络和卷。守护进程还可以与其他守护进程通信来管理Docker服务。
(2) REST API: 主要与Docker Daemon进行交互,比如Docker Cli或者直接调用REST API;
(3) 客户端(Docker Client): 它是与Docker交互的主要方式通过命令行接口(CLI)客户端(docker命
令),客户机将命令通过REST API发送给并执行其命令;
(4) Register Repository 镜像仓库: Docker注册表存储Docker镜像,可以采用Docker Hub是公共
注册仓库,或者采用企业内部自建的Harbor私有仓库;
(5) Image 镜像: 映像是一个只读模板,带有创建Docker容器的指令。映像通常基于另一个映像,还
需要进行一些额外的定制,你可以通过Docker Hub公共镜像仓库进行拉取对应的系统或者应用镜
像;
(6) Container 容器: 容器是映像的可运行实例。您可以使用Docker API或CLI创建、启动、停止、
移动或删除容器。您可以将一个容器连接到一个或多个网络,将存储附加到它,甚至根据它的当前
状态创建一个新映像。
(7) Services : Docker引擎支持集群模式服务允许您跨多个Docker守护进程()扩展管理容器,服务允
许您定义所需的状态,例如在任何给定时间必须可用的服务副本的数量。默认情况下,服务在所有
工作节点之间进行负载平衡。对于使用者来说Docker服务看起来是一个单独的应用程序;

Docker 的核心组件包括:

  1. Docker 客户端 - Client
  2. Docker 服务器 - Docker daemon
  3. Docker 镜像 - Image
  4. Registry
  5. Docker 容器 - Container

Docker 内部具体实现:

  1. 用户是使用Docker Client与Docker Daemon建立通信,并发送请求给后者。
  2. Docker Daemon作为Docker架构中的主体部分,首先提供Docker Server的功能使其可以接受
    Docker Client的请求。
  3. Docker Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
  4. Job的运行过程中,当需要容器镜像时,则从DockerRegistry中下载镜像,并通过镜像管理驱动
    Graph driver将下载镜像以Graph的形式存储。
  5. 当需要为Docker创建网络环境时,通过网络管理驱动Networkdriver创建并配置Docker容器网络环
    境。
  6. 当需要限制Docker容器运行资源或执行用户指令等操作时,则通过Exec driver来完成。
  7. Libcontainer是一项独立的容器管理包,Networkdriver以及Execdriver都是通过Libcontainer来实
    现具体对容器进行的操作。

Docker 客户端

最常用的 Docker 客户端是 docker 命令。通过 docker 我们可以方便地在 Host 上构建和运行容器。

docker 支持很多操作(子命令),后面会逐步用到。

[root@docker ~]# docker
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Common Commands:
run Create and run a new container from an image
exec Execute a command in a running container
ps List containers
build Build an image from a Dockerfile
pull Download an image from a registry
push Upload an image to a registry
images List images
login Log in to a registry
logout Log out from a registry
search Search Docker Hub for images
version Show the Docker version information
info Display system-wide information
Management Commands:
builder Manage builds
buildx* Docker Buildx
compose* Docker Compose
container Manage containers
context Manage contexts
image Manage images
manifest Manage Docker image manifests and manifest lists
network Manage networks
plugin Manage plugins
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Swarm Commands:
swarm Manage Swarm
Commands:
attach Attach local standard input, output, and error streams to a running
container
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
export Export a container's filesystem as a tar archive
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by
default)
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
wait Block until one or more containers stop, then print their exit
codes
Global Options:
--config string Location of client config files (default
"/root/.docker")
-c, --context string Name of the context to use to connect to the daemon
(overrides
DOCKER_HOST env var and default context set with
"docker context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket to connect to
-l, --log-level string Set the logging level ("debug", "info", "warn",
"error", "fatal")
(default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default
"/root/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default
"/root/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/root/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Run 'docker COMMAND --help' for more information on a command.
For more help on how to use Docker, head to https://docs.docker.com/go/guides/

除了 docker 命令行工具,用户也可以通过 REST API 与服务器通信。

Docker 服务器

Docker daemon 是服务器组件,以 Linux 后台服务的方式运行

● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor
preset: disabled)
Active: active (running) since Mon 2024-09-09 21:40:20 CST; 24h ago
Docs: https://docs.docker.com
Main PID: 2309 (dockerd)
Tasks: 10
Memory: 136.0M
CGroup: /system.slice/docker.service
└─2309 /usr/bin/dockerd -H fd:// --
containerd=/run/containerd/containerd.sock
Sep 09 21:40:16 docker systemd[1]: Starting Docker Application Container
Engine...
Sep 09 21:40:17 docker dockerd[2309]: time="2024-09-09T21:40:17.800157335+08:00"
level=info msg="Star>
Sep 09 21:40:18 docker dockerd[2309]: time="2024-09-09T21:40:18.196566715+08:00"
level=info msg="[gra>
Sep 09 21:40:18 docker dockerd[2309]: time="2024-09-09T21:40:18.208666863+08:00"
level=info msg="Load>
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.587647943+08:00"
level=info msg="Defa>
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.881267095+08:00"
level=info msg="Load>
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.956577565+08:00"
level=info msg="Dock>
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.957045036+08:00"
level=info msg="Daem>
Sep 09 21:40:20 docker dockerd[2309]: time="2024-09-09T21:40:20.014584201+08:00"
level=info msg="API >
Sep 09 21:40:20 docker systemd[1]: Started Docker Application Container Engine.

Docker 镜像
可将 Docker 镜像看着只读模板,通过它可以创建 Docker 容器。
例如某个镜像可能包含一个 Ubuntu 操作系统、一个 Apache HTTP Server 以及用户开发的 Web 应用。
镜像有多种生成方法:

  1. 可以从无到有开始创建镜像
  2. 也可以下载并使用别人创建好的现成的镜像
  3. 还可以在现有镜像上创建新的镜像
    我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile,通过执行
    docker build 命令可以构建出 Docker 镜像,后面我们会讨论。
    Docker 容器
    Docker 容器就是 Docker 镜像的运行实例。
    用户可以通过 CLI(docker)或是 API 启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜
    像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
    Registry
    Registry 是存放 Docker 镜像的仓库,Registry 分私有和公有两种。
    Docker Hub(https://hub.docker.com/) 是默认的 Registry,由 Docker 公司维护,上面有数以万计
    的镜像,用户可以自由下载和使用。

在这里插入图片描述
1.Docker 客户端执行 docker run

  1. Docker daemon 发现本地没有 httpd 镜像。
  2. daemon 从 Docker Hub 下载镜像。
  3. 下载完成,镜像 httpd 被保存到本地。
  4. Docker daemon

命令。

启动容器。

docker images 可以查看到 httpd 已经下载到本地。

[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 9cb0a2315602 7 weeks ago 148MB

第3章 镜像

06最小的镜像

镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。
本章内容安排如下:

  1. 首先通过研究几个典型的镜像,分析镜像的内部结构。
  2. 然后学习如何构建自己的镜像。
  3. 最后介绍怎样管理和分发镜像。
    镜像的内部结构
    为什么我们要讨论镜像的内部结构?
    如果只是使用镜像,当然不需要了解,直接通过 docker 命令下载和运行就可以了。
    但如果我们想创建自己的镜像,或者想理解 Docker 为什么是轻量级的,就非常有必要学习这部分知识
    了。
    我们从一个最小的镜像开始吧。
    hello-world - 最小的镜像
    hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。
    我们先通过 docker pull 从 Docker Hub 下载它。
[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest

用 docker images 命令查看镜像的信息。

[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d2c94e258dcb 16 months ago 13.3kB

hello-world 镜像竟然还不到 14KB!

通过 docker run 运行。

[root@docker ~]# docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
  1. base 镜像只是在用户空间与发行版一致,kernel 版本与发型版是不同的。
    例如 ubuntu使用 3.x.x 的 kernel,如果 Docker Host 是 CentOS Stream 8(比如我们的实验环
    境),那么在 CentOS 容器中使用的实际是是 Host 4.18.0 的 kernel。
[root@docker ~]# uname -r
4.18.0-553.6.1.el8.x86_64
#Host OS kernel 为 4.18.0

启动一个ubuntu,ubuntu内核正常应该与host os(centos stream 8)不一致

[root@docker ~]# docker run -it ubuntu
root@4264749aa4af:/# uname -r
4.18.0-553.6.1.el8.x86_64 #容器ubuntu用的内核就是docker host内核

启动一个centos:7,centos7正常内核为3.10

[root@docker ~]# docker run -it centos:7
[root@72397b60bb10 /]# uname -r
4.18.0-553.6.1.el8.x86_64
07 构建镜像

对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件
等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那
些官方镜像,因为 Docker 的工程师知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如:

1.找不到现成的镜像,比如自己开发的应用程序。

2.需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。
所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理
解。

Docker 容器文件系统
描述:从下面的图片可以看见出以下几点:
Docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的;
Docker 的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的;
Docker 中每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据;
Docker 镜像生产容器后会在此基础之上加入挂载点到安装Docker宿主机文件系统之中,并提供一个读写层(Read-Write Layer),所以容器进程的所有操作都在读写层进行;

Docker 提供了两种构建镜像的方法:

  1. docker commit 命令
  2. Dockerfile 构建文件

docker commit

docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

  1. 运行容器
  2. 修改容器
  3. 将容器保存为新的镜像

举个例子:在 ubuntu base 镜像中安装 vim并保存为新镜像。

  1. 第一步, 运行容器
[root@docker ~]# docker run -it ubuntu
root@8dbdff6d3d88:/#

-it 参数的作用是以交互模式进入容器,并打开终端。 d11014d4b667 是容器的内部 ID。

  1. 安装 vim

确认 vim 没有安装。

root@8dbdff6d3d88:/# vim
bash: vim: command not found

安装 vim。

root@8dbdff6d3d88:/# apt-get update
root@8dbdff6d3d8:/# apt-get install -y vim
1. Africa 2. America 3. Antarctica 4. Arctic 5. Asia 6. Atlantic 7.
Australia 8. Europe 9. Indian 10. Pacific 11. Etc
Geographic area: 5
Please select the city or region corresponding to your time zone.
1. Aden 12. Bangkok 23. Dili 34. Istanbul 45.
Krasnoyarsk 56. Novosibirsk 67. Samarkand 78. Tokyo
2. Almaty 13. Barnaul 24. Dubai 35. Jakarta 46.
Kuala_Lumpur 57. Omsk 68. Seoul 79. Tomsk
3. Amman 14. Beirut 25. Dushanbe 36. Jayapura 47. Kuching
58. Oral 69. Shanghai 80. Ulaanbaatar
4. Anadyr 15. Bishkek 26. Famagusta 37. Jerusalem 48. Kuwait
59. Phnom_Penh 70. Singapore 81. Urumqi
5. Aqtau 16. Brunei 27. Gaza 38. Kabul 49. Macau
60. Pontianak 71. Srednekolymsk 82. Ust-Nera
6. Aqtobe 17. Chita 28. Harbin 39. Kamchatka 50. Magadan
61. Pyongyang 72. Taipei 83. Vientiane
7. Ashgabat 18. Choibalsan 29. Hebron 40. Karachi 51. Makassar
62. Qatar 73. Tashkent 84. Vladivostok
8. Atyrau 19. Chongqing 30. Ho_Chi_Minh 41. Kashgar 52. Manila
63. Qostanay 74. Tbilisi 85. Yakutsk
9. Baghdad 20. Colombo 31. Hong_Kong 42. Kathmandu 53. Muscat
64. Qyzylorda 75. Tehran 86. Yangon
10. Bahrain 21. Damascus 32. Hovd 43. Khandyga 54. Nicosia
65. Riyadh 76. Tel_Aviv 87. Yekaterinburg
11. Baku 22. Dhaka 33. Irkutsk 44. Kolkata 55.
Novokuznetsk 66. Sakhalin 77. Thimphu 88. Yerevan
Time zone: 69
  1. 保存为新镜像

打开一个新窗口中查看当前运行的容器。

[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
8dbdff6d3d88 ubuntu "/bin/bash" 2 minutes ago Up 2 minutes
cool_darwin

上面查看结果的解释如下:

8dbdff6d3d88 是新创建容器的ID

cool_darwin 是 Docker 为我们的容器随机分配的名字。

执行 docker commit 命令将容器保存为镜像。

[root@docker ~]# docker commit cool_darwin ubuntu-with-vim #cool_darwin是容器
名,ubuntu-with-vim是新建的镜像名
sha256:ba18ae460c068f9bdba060350e64dcec4bc4af05b9918602ee34e6350d0369a4

新镜像命名为 ubuntu-with-vim 。

查看新镜像的属性。

[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest acefd029083b 27 minutes ago 189MB
ubuntu latest edbfe74c41f8 5 weeks ago 78.1MB

从 size 上看到镜像因为安装了软件而变大了。

从新镜像启动容器,验证 vim 已经可以使用。

[root@docker ~]# docker run -it ubuntu-with-vim #ubuntu-with-vim是新创建的镜像名
root@4d071cf3014f:/# which vim
/usr/bin/vim
root@4d071cf3014f:/# vim file1

以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像

原因如下:

  1. 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也

加入 vim,还得重复前面的所有步骤。

  1. 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进

行审计,存在安全隐患。

既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?

原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。

学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

下一节我们学习如何通过 Dockerfile 构建镜像。

013 Dockerfile*构建镜像

Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。

Dockerfile****内容基础知识:

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

常用参数:

docker build -f [Dockerfile路径] [构建上下文路径]

第一个 Dockerfile

用 Dockerfile 创建上节的 ubuntu-with-vim,其内容则为:

[root@docker ~]# cd /root #生产环境一般新建一个目录,里面写
Dockerfile,一个项目
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .

在这里插入图片描述

① 当前目录为 /root。

② Dockerfile 准备就绪。

③ 运行 docker build 命令, -t 将新镜像命名为 ubuntu-with-vim-dockerfile ,命令末尾的 . 指明

build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -

f 参数指定 Dockerfile 的位置。

④ 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker

daemon。build context 为镜像构建提供所需要的文件或目录。

Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context

为当前目录 /root ,该目录下的所有文件和子目录都会被发送给 Docker daemon。

所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 / 、 /usr 作

为 build context,否则构建过程会相当缓慢甚至失败。

⑤ Step 1:执行 FROM ,将 ubuntu 作为 base 镜像。

⑥ Step 2:执行 RUN ,安装 vim

⑦ 镜像构建成功。

[root@docker ~]# cd /root #生产环境一般新建一个目录,里面写

Dockerfile,一个项目

[root@docker ~]# vim Dockerfile

FROM ubuntu

RUN apt-get update && apt-get install -y vim

[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .⑧ 镜像重命名为ubuntu-with-vim-dockerfile

通过 docker images 查看镜像信息。

[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile latest bbc08145d011 10 minutes ago 189MB
ubuntu-with-vim latest acefd029083b 27 minutes ago 189MB
ubuntu latest edbfe74c41f8 5 weeks ago 78.1MB
08 镜像的缓存特性

上一节我们学习了镜像的分层结构,接下来讨论镜像的缓存特性。

Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创

建。

举例说明。

在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:

[root@docker ~]# pwd
/root
[root@docker ~]# ls
Dockerfile
[root@docker ~]# touch testfile
[root@docker ~]# ls
Dockerfile testfile
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
COPY testfile /
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-2 .
[root@docker ~]# vim Dockerfile
FROM ubuntu
COPY testfile /
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-3 .
09调试Dockerfile

包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,所以本节讨论如

何 debug Dockerfile。

先回顾一下通过 Dockerfile 构建镜像的过程:

  1. 从 base 镜像运行一个容器。
  2. 执行一条指令,对容器做修改。
  3. 执行类似 docker commit 的操作,生成一个新的镜像层。
  4. Docker 再基于刚刚提交的镜像运行一个新容器。
  5. 重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。

从这个过程可以看出,如果 Dockerfile 由于某种原因执行到某个指令失败了,我们也将能够得到前一个

指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。我们可以运行最新的这个镜像定位指令

失败的原因。

我们来看一个调试的例子。Dockerfile 内容如下:

执行 docker build :

[root@docker ~]# ls #查看下有没有Dockerfile和testfile
Dockerfile testfile
[root@docker ~]# vim Dockerfile #编辑Dockerfile,写入上图的内容
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /
[root@docker ~]# docker build -t image-debug . #基于刚才写的Dockerfile构建镜像
image-debug
# 找出错误原因,修改错误
[root@docker ~]# vim Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/sh -c "echo continue to builld..." #将错误的/bin/bash修改为正确
的/bin/sh
COPY testfile /
[root@docker ~]# docker build -t image-debug . #基于刚才写的Dockerfile构建镜像
image-debug
010 Dockerfile常用指令

是时候系统学习 Dockerfile 了。

下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。

FROM

指定 base 镜像。第一条必须是FROM

MAINTAINER

设置镜像的作者,可以是任意字符串。

COPY

将文件从 build context 复制到镜像。

COPY 支持两种形式:

  1. COPY src dest
  2. COPY [“src”, “dest”]

注意:src 只能指定 build context 中的文件或目录。

ADD

与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz

等),文件会被自动解压到 dest。

# 找出错误原因,修改错误

[root@docker ~]# vim Dockerfile

FROM busybox

RUN touch tmpfile

RUN /bin/sh -c “echo continue to builld…” #将错误的/bin/bash修改为正确

的/bin/sh

COPY testfile /

[root@docker ~]# docker build -t image-debug . #基于刚才写的Dockerfile构建镜像

image-debug

ENV

设置环境变量,环境变量可被后面的指令使用。例如:

...
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
...
EXPOSE

指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。

VOLUME

将文件或目录声明为 volume。我们会在容器存储部分详细讨论。

WORKDIR

为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。

RUN

在容器中运行指定的命令。

CMD

容器启动时运行指定的命令。

Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。

ENTRYPOINT

设置容器启动时运行的命令。

Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会

被当做参数传递给 ENTRYPOINT。

下面我们来看一个较为全面的 Dockerfile:

# my dockerfile
FROM busybox #从busybox开始构建
MAINTAINER 6946630@qq.com #声明作者信息
WORKDIR /testdir #设置工作目录为/testdir
RUN touch tmpfile1 #在新镜像中创建tmpfille1
COPY ["tmpfile2","."] #将Dockerfile文件所在目录中的tmpfile2文件拷贝到新镜
像中
ADD ["passwd.tar.gz","."] #将Dockerfile文件所在目录中的passwd.tar.gz拷贝到
新镜像中并解压缩
ENV WELCOME "You are in my container,welcome!" #设置环境变量为WELOCME

完整的操作步骤如下:

[root@docker ~]# pwd #确定Dockerfile工作目录
/root
[root@docker ~]# ls #当前/root目录下空的
[root@docker ~]# touch tmpfile2 #创建空文档tmpfile2
[root@docker ~]# cp /etc/passwd .             #将/etc/passwd文件拷贝到/root
[root@docker ~]# tar -cvzf passwd.tar.gz passwd     #将passwd文件做出归档文件
passwd.tar.gz
passwd
[root@docker ~]# rm passwd #删除passwd文件
rm: remove regular file 'passwd'? y
[root@docker ~]# vim Dockerfile #编辑Dockerfile写入如下内容
# my dockerfile
FROM busybox
MAINTAINER 6946630@qq.com
WORKDIR /testdir
RUN touch tmpfile1
COPY ["tmpfile2","."]
ADD ["passwd.tar.gz","."]
ENV WELCOME "You are in my container,welcome!"
[root@docker ~]# ls #最后目录中有三个文件
Dockerfile passwd.tar.gz tmpfile2
[root@docker ~]# docker build -t my-image . #构建新镜像my-image
011 RUN vs CMD vs ENTRYPOINT

RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。本节将通过实践详细

讨论它们的区别。

简单的说:

  1. RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
  2. CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参

数替换。

  1. ENTRYPOINT 配置容器启动时运行的命令。

下面我们详细分析。

Shell Exec 格式

我们可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式,二者在使

用上有细微的区别。

Shell 格式

<instruction> <command>

例如:

RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"

当指令执行时,shell 格式底层会调用 /bin/sh -c 。

例如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name gqd
ENTRYPOINT echo "Hello, $name"

用上面的Dockerfile创建镜像dockerfile1用于测试

[root@docker ~]# docker build -t dockerfile1 .

执行 docker run dockerfile1:

[root@docker ~]# docker run dockerfile1
Hello,cj

注意环境变量 name 已经被值 gqd 替换。

下面来看 Exec 格式。

Exec 格式

<instruction> ["executable", "param1", "param2", ...]

例如:

RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]

当指令执行时,会直接调用 ,不会被 shell 解析。

例如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name gqd
ENTRYPOINT ["/bin/echo","Hello, $name"]

用上面的Dockerfile创建镜像dockerfile2用于测试

[root@docker ~]# docker build -t dockerfile2 .

执行 docker run dockerfile2:

[root@docker ~]# docker run dockerfile2
hello,$name

注意环境变量“name”没有被替换。

如果希望使用环境变量,照如下修改

[root@docker ~]# vim Dockerfile
FROM busybox
ENV name gqd
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]

用上面的Dockerfile创建镜像dockerfile3用于测试

[root@docker ~]# docker build -t dockerfile3 .

执行 docker run dockerfile3:

[root@docker ~]# docker run dockerfile3
Hello, cj

CMD ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可

以。

RUN

RUN 指令通常用于安装应用和软件包。

RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。

RUN 有两种格式:

  1. Shell 格式:RUN
  2. Exec 格式:RUN [“executable”, “param1”, “param2”]

下面的Dockerfile是使用 RUN 安装多个包的例子:

[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

用上面的Dockerfile创建镜像dockerfile4用于测试

[root@docker ~]# docker build -t dockerfile4 .

执行 docker run -it dockerfile4:

[root@docker ~]# docker run -it dockerfile4
root@43894b9f29db:/# apt list install brz cvs git mercurial subversion
Listing... Done
brz/noble,now 3.3.5-6build2 amd64 [installed,automatic]
cvs/noble,now 2:1.12.13+real-30build1 amd64 [installed]
git/noble-updates,noble-security,now 1:2.43.0-1ubuntu7.3 amd64 [installed]
mercurial/noble-updates,now 6.7.2-1ubuntu2.2 amd64 [installed]
subversion/noble,now 1.14.3-1build4 amd64 [installed]

注意:apt-get update apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最

新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层

可能是很久以前缓存的。

CMD

CMD 指令允许用户指定容器的默认执行的命令。

此命令会在容器启动且 docker run 没有指定其他命令时运行。

  1. 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
  2. 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。

CMD 有三种格式:

  1. Exec 格式:CMD [“executable”,“param1”,“param2”] 这是 CMD 的推荐格式。
  2. CMD [“param1”,“param2”] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec

格式。

  1. Shell 格式:CMD command param1 param2

Exec 和 Shell 格式前面已经介绍过了。

第二种格式 CMD [“param1”,“param2”] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是为

ENTRYPOINT 设置默认的参数。我们将在后面讨论 ENTRYPOINT 时举例说明。

下面看看 CMD 是如何工作的。Dockerfile 如下:

[root@docker ~]# vim Dockerfile
FROM busybox
CMD echo "Hello,world

用上面的Dockerfile创建镜像dockerfile5用于测试

[root@docker ~]# docker build -t dockerfile5 .

运行容器docker run -it dockerfile5将输出:

[root@docker ~]# docker run -it dockerfile5
Hello,world

但当后面加上一个命令,比如docker run -it dockerfile5 /bin/sh,CMD 会被忽略掉,命令 sh 将被执行:

[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #

ENTRYPOINT

ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。

ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于

ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。ENTRYPOINT 有两种格式:

  1. Exec 格式:ENTRYPOINT [“executable”, “param1”, “param2”] 这是 ENTRYPOINT 的推荐格式。
  2. Shell 格式:ENTRYPOINT command param1 param2

在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。

Exec 格式

ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。

比如下面的 Dockerfile :

[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

用上面的Dockerfile创建镜像dockerfile6用于测试

[root@docker ~]# docker build -t dockerfile6 .

当容器通过 docker run -it dockerfile6 启动时,输出为:

[root@docker ~]# docker run -it dockerfile6
Hello world

而如果通过 docker run -it dockerfile6 gqd 启动,则输出为:

[root@docker ~]# docker run -it dockerfile6 gqd
Hello gqd

Shell 格式

ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

比如下面的 Dockerfile :

FROM busybox
ENTRYPOINT echo "Hello"
CMD ["world"]

用上面的Dockerfile创建镜像dockerfile7用于测试

[root@docker ~]# docker build -t dockerfile7 .

当容器通过 docker run -it dockerfile7 启动时,输出为:

[root@docker ~]# docker run -it dockerfile7
Hello

而如果通过 docker run -it dockerfile7 gqd 启动,则输出为:

[root@docker ~]# docker run -it dockerfile7 gqd
Hello

Shell 格式

最佳实践

  1. 使用 RUN 指令安装应用和软件包,构建镜像。

  2. 如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。

  3. 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。

到这里,我们已经具备编写 Dockerfile 的能力了。如果大家还觉得没把握,推荐一个快速掌握Dockerfile 的方法: Docker Hub 上参考那些官方镜像的 Dockerfile

好了,我们已经学习完如何创建自己的 image,下一节讨论如何分发 image。

Dockerfile案例:配置*SSH镜像

项目背景:官方下载的centos镜像默认不带ssh,管理起来不方便,自己制作一个带SSH功能的

centos镜像

创建dockerfile

[root@docker ~]# vim centos.ssh.dockerfile
FROM centos:8.4.2105
MAINTAINER cj
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:huawei" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]

构建镜像

[root@docker ~]# docker build -t centos:ssh -f centos.ssh.dockerfile .
[+] Building 0.6s (10/10) FINISHED
docker:default
=> [internal] load build definition from centos.ssh.dockerfile
0.0s
=> => transferring dockerfile: 604B
0.0s
=> [internal] load metadata for docker.io/library/centos:8.4.2105
0.0s
=> [internal] load .dockerignore
0.0s
=> => transferring context: 2B
0.0s
=> [1/6] FROM docker.io/library/centos:8.4.2105
0.0s
=> CACHED [2/6] RUN minorver=8.4.2105 && sed -e "s|^mirrorlist=|#mirrorlist=|g"
-e "s|^#baseurl=http://mirror.centos.org/$contentdir/$releasever|baseurl=h 0.0s
=> CACHED [3/6] RUN yum install -y openssh-server
0.0s
=> CACHED [4/6] RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
0.0s
=> [5/6] RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
0.3s
=> [6/6] RUN echo "root:huawei" | chpasswd
0.3s
=> exporting to image
0.0s
=> => exporting layers
0.0s
=> => writing image
sha256:cc138c4d3c36fe82eab32dd80549707c8bfe99ddcb6d3882319a10283bb1a864
0.0s
=> => naming to docker.io/library/centos:ssh

查看现象

[root@docker ~]# docker history centos:ssh
IMAGE CREATED CREATED BY
SIZE COMMENT
cc138c4d3c36 36 seconds ago CMD ["/usr/sbin/sshd" "-D"]
0B buildkit.dockerfile.v0
<missing> 36 seconds ago EXPOSE map[22/tcp:{}]
0B buildkit.dockerfile.v0
<missing> 36 seconds ago RUN /bin/sh -c echo "root:huawei" | chpasswd…
1.77kB buildkit.dockerfile.v0
<missing> 36 seconds ago RUN /bin/sh -c ssh-keygen -t ecdsa -f /etc/s…
695B buildkit.dockerfile.v0
<missing> 47 minutes ago RUN /bin/sh -c ssh-keygen -t rsa -f /etc/ssh…
3.18kB buildkit.dockerfile.v0
<missing> 47 minutes ago RUN /bin/sh -c yum install -y openssh-server…
51.9MB buildkit.dockerfile.v0
<missing> About an hour ago RUN /bin/sh -c minorver=8.4.2105 && sed -e "…
17.6kB buildkit.dockerfile.v0
<missing> About an hour ago MAINTAINER gaoqiaodong
0B buildkit.dockerfile.v0
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"]
0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc…
0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0…
231MB

测试

#基于刚才dockerfile创建的镜像centos:ssh创建容器sshtest
[root@docker ~]# docker run -d -p 2022:22 --name sshtest centos:ssh
73d963d15407a1e73097540bb320b9edf05b468001bd707abf01bc7be5e54bcb
#创建出来的容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
73d963d15407 centos:ssh "/usr/sbin/sshd -D" 6 seconds ago Up 5 seconds
0.0.0.0:2022->22/tcp, :::2022->22/tcp sshtest
#ssh登录容器测试ssh,能够成功登录
[root@docker ~]# ssh root@localhost -p 2022
The authenticity of host '[localhost]:2022 ([::1]:2022)' can't be established.
ECDSA key fingerprint is SHA256:z1owYLOuClnbPrZwXxgy1jcItQT1k+QX6LxosydT64A.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts.
root@localhost's password:
"System is booting up. Unprivileged users are not permitted to log in yet. Please
come back later. For technical details, see pam_nologin(8)."
[root@73d963d15407 ~]#

Dockerfile案例:自定义httpd镜像

创建dockerfile

[root@docker ~]# vim httpd.dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y httpd && yum clean all && rm -rf /var/cache/yum
COPY index.html /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
[root@docker ~]# echo Hello World > index.html

构建镜像

[root@docker ~]# docker build -t httpd:centos -f httpd.dockerfile .

查看现象

[root@docker ~]# docker history httpd:centos

测试

#基于刚才dockerfile创建的镜像httpd:centos创建容器myweb
[root@docker ~]# docker run -d -p 80:80 --name myweb httpd:centos
1e0b0631cf708bcc0a162d56b936a12cd06c9bcdebcc2594b23c2fbee3ed8894
#创建出来的容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
1e0b0631cf70 httpd:centos "/usr/sbin/httpd -DF…" About a minute ago Up
About a minute 0.0.0.0:80->80/tcp, :::80->80/tcp myweb
#访问测试
[root@docker ~]# curl localhost
Hello World

Dockerfile案例:自定义mycentosjava8

项目背景:java工程师需要我们给他发个带JAVA的centos

要求CentOS8镜像具备vim+ifconfig+jdk8

JDK的下载地址:https://www.oracle.com/java/technologies/downloads/#java8

编写Dockerfile文件

[root@docker ~]# mkdir myfile ; cd myfile
[root@docker myfile]# vim Dockerfile
FROM centos:8.4.2105
MAINTAINER cj<2438115347@qq.com>

# 定义工作目录变量
ENV MYPATH /usr/local
WORKDIR $MYPATH

# 配置 yum 源 (注意:\ 后面不能有空格)
RUN minorver=8.4.2105 \
    && sed -e "s|^mirrorlist=|#mirrorlist=|g" -e \
    "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo

# 安装 vim 编辑器
RUN yum -y install vim
# 安装 ifconfig 命令查看网络 IP
RUN yum -y install net-tools
# 安装 java8 及 lib 库
RUN yum -y install glibc.i686

# 创建 Java 目录
RUN mkdir /usr/local/java

# ADD 是相对路径 jar, 把 jdk-8u461-linux-x64.tar.gz 添加到容器中
# 确保该文件与 Dockerfile 在同一目录
ADD jdk-8u461-linux-x64.tar.gz /usr/local/java/

# 配置 java 环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_461
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

EXPOSE 80

# 合并 CMD 指令,容器启动后执行 echo 并进入 bash
CMD echo $MYPATH && echo "success-------ok" && /bin/bash
# 将jdk-8u461-linux-x64.tar.gz与Dockerfile放到同一目录
[root@docker myfile]# ls
Dockerfile jdk-8u461-linux-x64.tar.gz
# 构建镜像为centosjava8:461
[root@docker myfile]# docker build -t centosjava8:461 .

查看构建的镜像

[root@docker myfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centosjava8 461 8097b762c0a5 4 minutes ago 537MB

用创建的镜像运行容器测试

[root@docker myfile]# docker run -it centosjava8:461 /bin/bash
[root@6b2767bcdf24 local]# pwd #测试了ENV和WORKDIR
/usr/local
[root@6b2767bcdf24 local]# java -version #测试java是否安装
java version "1.8.0_461"
Java(TM) SE Runtime Environment (build 1.8.0_461-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.461-b11, mixed mode)
[root@6b2767bcdf24 local]# ifconfig #测试net-tools是否安装
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:03 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@6b2767bcdf24 local]#vim file1 #测试vim是否安装
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐