Docker学习笔记
Kubernetes 团队近日宣布将在最新版本中弃用 Docker 支持的功能,后续版本会陆续删除这些功能虽然目前容器市场 Docker 还是占用很大的比例,但被弃用的结局已定,在这个过渡期中,不妨去拥抱Containerd 和 Podman吧!点此了解
教程来源:
- 深入浅出docker
- docker进阶与实战 华为docker实践小组 【书写于2015年】
Docker 官网:https://www.docker.com 【Build, Ship and Run】
Github Docker 源码:https://github.com/docker/docker-ce
※. 学习网站
※,Docker-CE和Docker-EE
- Docker-CE指Docker社区版,由社区维护和提供技术支持,为免费版本,适合个人开发人员和小团队使用。
- Docker-EE指Docker企业版,为收费版本,由售后团队和技术团队提供技术支持,专为企业开发和IT团队而设计。相比Docker-CE,增加一些额外功能,更重要的是提供了更安全的保障。此外,Docker的发布版本分为Stable版和Edge版,区别在于前者是按季度发布的稳定版(发布慢),后者是按月发布的边缘版(发布快)。
- 通常情况下,Docker-CE足以满足我们的需求。
※,常见问题记录:参考一篇文章
※,docker pull镜像时,报错 408,如下:
error pulling image configuration: download failed after attempts=1: error parsing HTTP 408 response body: invalid character '<' looking for beginning of value: "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n"
解决方法:修改相关网卡MUT的值(原来一般是1500,修改成其他的即可):
ip link set dev <网卡名称> mtu 1400
※,重启docker,报错·docker.service: Start request repeated too quickly.·
原因:/etc/docker/daemon.json 格式不正确。参考文章
※,容器状态为create,并且docker rm <containerId>无法删除
解决方法:ps -ef|grep <containerId> 找出这个容器在物理机上的进程ID,kill -9 <进程ID> 即可删除这个状态为create的容器。
※,docker命令卡住:比如docker ps 不显示列表,docker exec 无法进入容器内部等等。
排查原因,发现是docker rm -f <id>的命令一直在占用进程。可能是服务端发现连接数太多了,就会阻塞新的命令。使用kill -9 <pid>后,命令就可以正常执行了!!!
※,停掉docker服务后,每次docker ps就会自动重启(启动):
- 停掉时有个提示:Warning: Stopping docker.service, but it can still be activated by: docker.socket
- 解决方法: ·systemctl stop docker.socket·
※,修改docker的storage driver(devicemapper, overlay2)
【背景介绍】某一台centos7(3.10.0-327.el7.x86_64)安装的docker(20.10.21)默认的存储引擎是devicemapper,启动docker时会提示devicemapper不建议在生产环境使用,于是想改为常用的overlay2.
有两种方式可以修改storage driver:
- 在 docker 启动的时候添加 “--storage-driver” flag.
- 停止docker
- 修改/usr/lib/systemd/system/docker.service,修改后为:`ExecStart=/usr/bin/dockerd --storage-driver overlay2 -H fd:// --containerd=/run/containerd/containerd.sock`
- systemctl daemon-reload; systemctl start docker;
- 修改/etc/docker/daemon.json
{ "storage-driver":"overlay2", "graph":"/home/docker" }
【问题】修改后再次启动docker时报错,无法启动,查看` cat /var/log/messages|grep docker`发现真正的错误信息为:Nov 14 15:27:57 dockerd: failed to start daemon: error initializing graphdriver: overlay2: the backing xfs filesystem is formatted without d_type support, which leads to incorrect behavior. Reformat the filesystem with ftype=1 to enable d_type support. Backing filesystems without d_type support are not supported.
【解决】参考此文。此问题出现的原因是docker存储所在的系统分区初始化时未支持d_type(可以使用xfs_info <partition-name>查看),需要重新用d_type=1重新格式化此分区。注意:这样会导致这个分区上的所有数据丢失。
※,
※,Docker 平时记录
- ·docker logs <containerId>` //查看容器日志,可以查看到容器启动失败或运行中失败的 报错信息!
- 容器日志存放位置为:`${docker root dir}/containers/${containerId}-json.log`。容器日志随容器的删除而删除。
- `docker logs <containerId> --no-trunc` //显示完全
- ·docker update --restart=always <container>` // docker update 命令可以更新容器的配置,但这个命令可操作的配置不多,docker update --help可查看可配置的项。
- ·docker stats· //查看docker容器的CPU占用率、内存占用率等信息。会实时刷新。·docker stats --help· 查看帮助。
- --no-stream 参数值打印一次快照而不会实时刷新。
- `docker cp /mnt/word-gen-job-1.0.24-SNAPSHOT.jar word-gen-job:/services/word-gen-job/lib/` //复制主机文件到docker容器中,反之则调换下顺序即可.其中word-gen-job是容器名称,也可以用容器ID。
- 容器与镜像的关系
- 【新发现,容器内目录,容器目录】docker exec -it apachepulsar/pulsar bash, 进入容器后看到的目录在宿主机上对应的目录为: docker inspect <containerId>|grep <MergedDir或UpperDir>对应的目录。比如:"MergedDir": "/var/lib/docker/overlay2/41234cc066fd3f4924cead088f4276a5290871a2afd190603df91b45ee058ca0/merged"
- docker exec -it --user root bash // --user root 以root用户进入容器。作用类似于 docker run 命令的--privileged=true 参数
- --user 0 或 -u root 或 -u 0都可以
- 为什么Docker镜像大小与仓库中不一致?
- hub中保持压缩状态,pull后会解压。
- docker system df -v //查看image ,container, volume具体占用空间
- docker容器中如果主进程退出了,那么容器也会退出。docker ps -a可以查看退出的错误码(0是正常退出)。Dockerfile中如果cmd写了个
cmd echo hello world
就会直接退出。阻止容器退出的方法是使用阻塞命令,如top,node之类的,或者启动一个服务,如Java服务。 - 1
- 1
- 1
- 1
- 1
※,docker各阶段如何使用代理:
Dockerd 代理
在执行
docker pull
时,是由守护进程dockerd
来执行。因此,代理需要配在dockerd
的环境中。而这个环境,则是受systemd
所管控,因此实际是systemd
的配置。sudo mkdir -p /etc/systemd/system/docker.service.d sudo touch /etc/systemd/system/docker.service.d/proxy.conf
在这个
proxy.conf
文件(可以是任意*.conf
的形式)中,添加以下内容:[Service] Environment="HTTP_PROXY=http://127.0.0.1:8888/" Environment="HTTPS_PROXY=http://127.0.0.1:8888/" Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
实际使用时发现,这里配置的代理生效了,但是NO_PROXY并没有生效,参考此文,但是依然没有生效。这个问题好像是某个版本(安装的是19.03.15)的bug,老版本是可以的。[更新:并非版本bug,应该和https有关,或许和squid的https配置等有关]
Same issue here. I’m on Docker 17.12, and the --no-proxy option is not working for me. Related issues on GitHub: https://github.com/docker/for-mac/issues/2506 https://github.com/openshift/origin/issues/18596 Looks like the only workaround now is to downgrade Docker to a version prior to 17.12.0.
Container 代理
在容器运行阶段,如果需要代理上网,则需要配置
~/.docker/config.json
。以下配置,只在Docker 17.07及以上版本生效。{ "proxies": { "default": { "httpProxy": "http://127.0.0.1:8888", "httpsProxy": "http://127.0.0.1.com:8888", "noProxy": "localhost,127.0.0.1,.example.com" } } }
这个是用户级的配置,除了
proxies
,docker login
等相关信息也会在其中。而且还可以配置信息展示的格式、插件参数等。此外,容器的网络代理,也可以直接在其运行时通过
-e
注入http_proxy
等环境变量。这两种方法分别适合不同场景。config.json
非常方便,默认在所有配置修改后启动的容器生效,适合个人开发环境。在CI/CD的自动构建环境、或者实际上线运行的环境中,这种方法就不太合适,用-e
注入这种显式配置会更好,减轻对构建、部署环境的依赖。当然,在这些环境中,最好用良好的设计避免配置代理上网。Docker Build 代理
虽然
docker build
的本质,也是启动一个容器,但是环境会略有不同,用户级配置无效。在构建时,需要注入http_proxy
等参数。docker build . \ --build-arg "HTTP_PROXY=http://proxy.example.com:8080/" \ --build-arg "HTTPS_PROXY=http://proxy.example.com:8080/" \ --build-arg "NO_PROXY=localhost,127.0.0.1,.example.com" \ -t your/image:tag
复制
注意:无论是
docker run
还是docker build
,默认是网络隔绝的。如果代理使用的是localhost:3128
这类,则会无效。这类仅限本地的代理,必须加上--network host
才能正常使用。而一般则需要配置代理的外部IP,而且代理本身要开启 Gateway 模式。重启生效
代理配置完成后,
reboot
重启当然可以生效,但不重启也行。
docker build
代理是在执行前设置的,所以修改后,下次执行立即生效。Container 代理的修改也是立即生效的,但是只针对以后启动的 Container,对已经启动的 Container 无效。
dockerd
代理的修改比较特殊,它实际上是改systemd
的配置,因此需要重载systemd
并重启dockerd
才能生效。sudo systemctl daemon-reload sudo systemctl restart docker
一、容器技术:
※,容器和虚拟机之间的不同:
虚拟机是用来进行硬件资源划分的完美解决方案,它利用了硬件虚拟化技术,通过一个hypervisor层来实现对资源的彻底隔离;而容器则是操作系统级别的虚拟化,是轻量级的虚拟化,利用的是内核的Cgroup(control group) 和 Namespace 特性,此功能完全通过软件来实现,仅仅是进程本身就可以与其他进程隔离开,不需要任何辅助。从更高的层面讲,虚拟机是硬件虚拟化,容器是操作系统虚拟化。
虚拟机会独占分配个自己的资源,几乎不存在资源共享,各个虚拟机实例之间近乎完全隔离,所以虚拟机更加重量级,也会消耗更多的资源。Docker容器与主机共享操作系统内核,不同的容器之间可以共享部分系统资源,因此容器更加轻量级,资源的消耗也少。我们可以很轻松的在一台普通的Linux机器上部署运行100个或更多的docker容器而不会占用太多的资源(如果容器中没有执行运算任务或io操作),而单台机器上不可能部署100台虚拟机,因为每一个虚拟机实例都会占用一个完整的操作系统所需要的所有资源。另外docker容器的启动很快,通常是秒级甚至是毫秒级的(容器本身启动很快,唯一对容器启动时间有影响的就是容器内应用启动所花费的时间),而虚拟机的启动虽然会快于物理机器,但是启动时间通常也是在数秒至数十秒的量级。
因此可以根据需求得不同选择相应的隔离方式。如果需要资源完全隔离并且不考虑资源消耗,可以选择使用虚拟机;而若是想隔离进程并且需要运行大量进程实例,则应该选择docker容器。
※,容器 与 Docker
※,容器技术是Docker的一项 基础/核心 技术。这里讲的容器技术指的是内核容器技术,即Linux Container,Docker是在内核容器技术(Cgroup + Namespace)的基础上提供了一个更高层的控制工具。
※,容器的组成:对于Linux容器的最小组成可以用如下公式表示(其中Cgroup + Namespace 是容器的核心技术):
·容器 = Cgroup + Namespace + rootfs + 容器引擎(用户态工具)` , 目前市场上所有的Linux容器项目都包含这些组件。各项功能如下:
- Cgroup: 资源控制
- Namespace: 访问隔离
- rootfs: 文件系统隔离。
- 所谓的rootfs(根文件系统)意思就是:那些能让操作系统正常运行的,文件夹和文件的大集合。换句话说,如果你的操作系统类型定下来了,是Linux还是Windows,那么,和此操作系统相关的,系统级别的,固定位置的,文件夹和文件,都必须是存在的,否则,很可能,或者说必然,会导致操作系统出现无法正常运行,甚至无法启动等等问题。
- 容器引擎: 生命周期控制
※. 容器技术是docker的一项核心技术,和Docker一起发展和被大家熟知的“微服务(micro service)” 把容器的优势发挥的淋漓尽致。容器作为Linux平台的轻量级虚拟化,其核心优势是跟内核的无缝融合,其在运行效率上的优势和极小的系统开销与需要将各个组件单独部署的微服务应用完美融合。在微服务的设计哲学下,容器必将和 docker一起得到更加广泛的应用和发展。
※. Docker 和容器是一种完美的融合和相辅相成的关系,他们不是唯一的搭配,但一定是最完美的组合。与其说是容器造就了Docker,不如说是它们造就了彼此:容器技术让Docker得到更多的应用和推广,Docker也使得容器技术被更多人熟知。
※,Kubernetes
Kubernetes是谷歌的一个开源项目,并且开源后迅速成为容器编排领域的领头羊。Kubernetes是Docker之上的一个平台,现在采用Docker实现其底层容器相关的操作。在未来,Kubernetes中的默认容器运行时可能由Docker替换为containerd。
※,
二、Docker:
※,启动、停止docker服务
- `systemctrl start docker` OR `service docker start`
- `systemctl stop docker` OR `service docker stop`
- ·systemctl staus docker` OR `service docker status` OR `systemctl is-active docker` //docker服务运行状态
※,OCI:Open Container Initiative, 开放容器计划
OCI是一个旨在对容器基础架构中的基础组件(如镜像格式与容器运行时)进行标准化的管理委员会。
※,docker架构
- Docker客户端:docker是一个典型的c/s架构的应用程序,docker发布时将客户端和服务端统一在一个二进制文件中。类比MySQL。
- Docker daemon:也叫 Docker Server 或 Docker Engine(也有人把docker client + docker server在一起叫做Docker Engine)。这是驱动整个Docker功能的核心引擎。daemon实现了Docker引擎的API。Docker客户端和服务端通信有多种方式,既可以在同一台机器上通过UNIX套接字通信,也可以通过网络连接远程通信。使用默认Linux安装时,客户端与daemon之间的通信是通过本地IPC/UNIX Socket完成的(/var/run/docker.sock);在windows上是通过名为npipe:////./pipe/docker_engine的管道(pipe)完成的。
- Docker 容器:容器提供了一个完整的、隔离的运行环境。
- Docker 镜像:镜像可以看做是未运行的容器。容器和镜像的关系 可以类比为 实例和类 的关系。
- Registry:即仓库注册中心,存储镜像(类似于GitHub)。Docker官方Registry地址: hub.docker.com
※,Docker 引擎介绍:
Docker引擎就像汽车引擎一样,都是模块化的,其组件是可以替换的。目前为止,Docker引擎由下列主要的组件构成:Docker Client,Docker Daemon, containerd以及runc。它们共同负责容器的创建和运行。
※,docker概览
`sudo usermod -aG <dockerGroup> curUserName` 将当前用户加入到docker用户组中,这样便不必每次都加sudo执行docker 命令了.其中dockerGroup一般是就是用户组docker,但是也有可能是root。可以通过查看 /var/run/docker.sock 文件(或通过报错信息中提示的无权限文件名称)所属用户组来找到这个组名。
- 如果没生效,运行·newgrp docker· 或者 ·newgrp root· //更新用户组。自测通过
- 如果还没生效,运行·sudo chmod a+rw /var/run/docker.sock· //未自测
- windows版docker包含:Docker client、 Docker daemon、Docker compose、Docker Machine、Docker Natory命令行。
- 查看帮助:
- ·docker --help·
- ·docker + 命令 + --help` // e.g. docker ps --help
- `man docker + 命令` // e.g. man docker ps
- `docker system info` === `docker info` //查看docker的一些系统配置信息
- `docker system df` // 查看docker的磁盘使用情况
- `docker version` // 显示docker client 和 docker server的版本详细信息。如果不显示docker server相关信息说明docker服务没有启动。
- `docker --version` // 只显示版本号
- Linux上的docker 客户端和daemon之间的通信是通过本地 IPC/UNIX Socket完成的(/var/run/docker.sock);在windows上是通过名为npipe:////./pipe/docker_engine的管道完成的。
※, Docker 查看系统信息 ·docker system --help`
Usage: docker system COMMAND
Manage Docker
Commands:
df Show docker disk usage
events Get real time events from the server
info Display system-wide information
prune Remove unused dataRun 'docker system COMMAND --help' for more information on a command.
※,docker image 介绍 `docker image --help`
※,镜像中不包含操作系统内核,因为容器都是共享所在Docker主机的内核。所以有时会说容器仅包含必要的操作系统(通常只有操作系统文件和文件系统对象)。
※,官方推荐使用 dockerviz 分析docker image. 此工具可以图形化展示docker image的层次关系。同一个仓库的镜像并不一定要有特别的关系,比如:Ubuntu14.04 和 Ubuntu14.04.2之间就没有任何共享层。
※,镜像和分层
1. Docker镜像由一些松耦合的只读镜像层组成,Docker负责堆叠这些镜像层,并将它们表示为单个统一的对象。查看Docker镜像分层的方法
- 方式一、`docker [ image ] pull ubuntu:latest`时,以 Pull complete 结尾的每一行都代表了镜像中某个被拉取的镜像层。
- 方式二、·docker image inspect ubuntu:latest· //这里的image不能省略,docker inspect == docker container inspect
- `docker [ image ] history ubuntu:latest`命令显示了镜像的构建历史记录,但其并不是严格意义上的镜像分层。例如有些dockerfile 中的指令并不会创建新的镜像层。比如ENV、EXPOSE以及ENTRYPOINT。不过这些命令会在镜像中添加元数据。
2. 所有的Docker镜像都起源于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上创建新的镜像层。例如:基于Ubuntu Linux 16.04创建一个新的镜像,这就是新的镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。如下图所示:最上层的文件7是文件5的的一个更新版本,这种情况下,上层镜像中的文件覆盖了下层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。在外部看来整个镜像只有6个文件而非7个文件。
3. 在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。Docker通过存储引擎(新版本采用了快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。Linux上可用的存储引擎有:AUFS, Overlay2, Device Mapper, Btrfs 以及 ZFS。Docker在windows上仅支持windowsfilter一种存储引擎,该引擎基于NTFS文件系统,实现了分层和CoW(写时复制)。不同存储引擎的最终效果和用户体验是完全一致的。
4. 共享镜像层:多个镜像间可以并且确实会共享镜像层。这样可以节省空间并节省性能。
※,镜像表示方法(基本可以类比git仓库):
remote-dockerhub.com/group/namespace/<repository>:<tag>
- remote-dockerhub.com 镜像仓库平台地址,如果省略则默认为docker官方镜像库。Google容器镜像托管平台:gcr.io 或 https://cloud.google.com/container-registry/
- group/namespace 组织/命名空间(如某用户名)
- repository 仓库名称。
- :tag镜像tag。类似于git仓库中的tag。如果拉取时没有指定tag,默认为 latest。但是注意 latest 并不保证这是最新版的镜像。
- layer 镜像是由一系列层组成,每层都用64位的十六进制表示,类似于git中的commit。
- image ID 镜像最上层的layer ID就是此镜像的image ID。
※,通过客户端CLI搜索Docker Hub
- ·docker search hello` // 搜索所有名称中含有hello的仓库,个人命名空间中含有hello的也会搜索出来
- ·docker search hello --filter is-official=true` //过滤出官方仓库
- ·docker search hello --filter is-automated=true` //只显示自动创建的仓库。
- ·docker search hello --limit 90` // 默认docker search 只显示25条记录,可以通过 --limit 增加返回行数,但是最多也只能有100条。
- ·docker search al --limit 99 | awk '{print NR " " $0)}'` // 借助 awk 命令打印每行的序号
- docker search 时列出tag
查看代码
###使用如下脚本 #!/bin/sh # # Simple script that will display docker repository tags. # # Usage: # $ docker-show-repo-tags.sh ubuntu centos for Repo in $* ; do curl -s -S "https://registry.hub.docker.com/v2/repositories/library/$Repo/tags/" | \ sed -e 's/,/,\n/g' -e 's/\[/\[\n/g' | \ grep '"name"' | \ awk -F\" '{print $4;}' | \ sort -fu | \ sed -e "s/^/${Repo}:/" done ### 脚本使用方法:列出ubuntu、centos的tags $ ./docker-show-repo-tags.sh ubuntu centos
※,拉取镜像
Docker客户端的镜像仓库服务(即镜像仓库托管平台)地址是可配置的,默认是Docker Hub。可以在 Docker的配置文件(Linux上默认位置为·/etc/docker/daemon.json·)中修改,如下:
$ vim /etc/docker/daemon.json // 阿里云的Docker仓库服务mirror 需要实名注册,地址:https://cr.console.aliyun.com { "registry-mirrors": [ "https://registry.docker-cn.com", "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com", "https://mirror.baidubce.com", "https://ccr.ccs.tencentyun.com", "https://hub.daocloud.io/", "https://xxxxx.mirror.aliyuncs.com" ] }
修改后需要重载配置文件才能生效:·systemctl reload docker·。如果依然不生效重启docker服务:systemctl restart docker。可以使用
docker info
查看修改是否生效。
systemctl daemon-reload
//这个命令是重载systemd的配置文件,即位于/usr/lib/systemd/system/目录下的配置文件(以及其他一些drop-in 配置)systemctl reload docker
//这个命令是重载docker的配置文件,即/etc/docker/daemon.json文件。这个systemctl reload对于docker是重载docker配置文件,对于其他的service貌似不是这个作用,试验了了下sshd和network服务,通过systemctl status sshd查看运行了systemctl reload sshd后,sshd服务重启了,network则是从active变成了inactive(dead)。所以慎用!Linux Docker主机本地镜像仓库通常位于 /var/lib/docker/<storage-driver>, 目前最先进的storage-driver叫做 overlay2。 由于Docker的镜像采用分层构建设计,所以这个目录下存储的是镜像的各个层(layer),并没有一个和<imageId>完全对应的文件。windows主机下则是:C:\ProgramData\docker\windowsfilter。
- `docker image pull xxx` == `docker pull xxx`
- ·docker pull ubuntu:latest`
- `docker pull -a ubuntu`
- -a 参数可以拉取仓库中的全部镜像(所有标签的镜像都会拉取下来)
- ·docker pull alpine@sha256:3c7497bf0c7af..........` // 基于摘要值拉取某个镜像。
- 注意:docker暂没有原生命令可以查询远程仓库中镜像的摘要,所以必须先通过tag方式拉取到本地才能获取这个镜像的摘要。摘要的作用实际场景如:某个镜像的tag是someTag, 使用中发现这个镜像有bug,将其修复后使用相同的标签someTag将更新的镜像重新推送会仓库,原镜像被覆盖,但在生产环境中的遗留了大量的运行中的容器,这些容器使用而镜像是修复前的还是修复后的便可以通过摘要区分。
- docker pull 还可以指定仓库服务地址,如:docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.6.5
※,理解 Docker 各种部件的存储路径(容器、镜像、卷、网络等)
- ·docker [system] info | grep Root· //查看Docker默认的存储位置(Root Dir)。默认是`/var/lib/docker`,这个目录中保存着各种信息,例如:容器数据、卷、构建文件、网络文件和集群数据。
- LowerDir 包含镜像的只读层,表示变更的读写层包含在 UpperDir 中。MergedDir 表示 UpperDir 和 LowerDir 合并的结果,Docker 用它来运行容器。WorkDir 是 overlay2 的内部目录,应该是空的。
- ·docker system prune -a· //清理,这是四个命令的合集。
- ·docker container prune· // 不存在-a参数
- `docker image prune -a` // image清理时可以加个 -a参数表示will remove all images without at least one container associated to them
- `docker volume prune` // 不存在-a参数
- `docker network prune` // 不存在-a参数
★,修改docker默认的存储位置(迁移、转移):参考此文,各种方法,总结如下:
- 查看默认存储位置:sudo docker info | grep "Docker Root Dir"
- 方法一:软连接,docker已经产生了数据适合这种方法。
- ·systemctl stop docker· //停掉docker服务
- 在大容量磁盘上创建新的存储路径:mkdir -p /data1
- `cp -a /var/lib/docker /data1` //将文件夹/var/lib/docker 复制到/data1目录下,data1目录下将会有docker文件夹
- 【天坑】:cp时一定要加参数-a(等同于-dpr),用于保留文件的所有属性,否则docker容器就可能由于权限问题导致报错!可能要折腾一整天了
- 要么用cp -a要么直接mv,这样应该都不会导致权限问题
- `rm -rf /var/lib/docker` //确认无误后,删除 /var/lib/docker
- ·ln -s /data1/docker /var/lib/docker·
- systemctl start docker
- 方法二:通过docker配置文件修改docker启动参数(`--graph=/var/lib/docker`)从而修改默认存储位置。如果docker是通过systemd服务的方式启动的,则启动的修改配置文件的地方很多,关键在于systemd的理解
- `/usr/lib/systemd/system/docker.service`中的ExecStart=/usr/bin/dockerd --graph /new-path/docker(这是systemd的配置文件),然后 systemctl daemon-reload,systemctl restart docker。
- 如果docker是1.12或以上的版本,则可以修改`/etc/docker/daemon.json`([这个配置文件不属于systemd,而是docker自身的]),,如下 ·{"registry-mirrors": ["http://7e61f7f9.m.daocloud.io"],"graph": "/new-path/docker"}·,然后systemctl restart docker即可。
- 问题1:默认存储位置迁移后,MySQL无法启动,docker logs 查看报错信息:mysqld: Can't create/write to file '/tmp/ib9PpP7Y' (Errcode: 13 - Permission denied)。
- 解决方法:docker inspect <containerId> | grep merged 找到容器对应的存储目录,将此目录的权限改为777即可!如果还不行,docker inspect 查找所有的mount 和volume,将其权限改为777。或者直接将整个默认存储目录改为777.
※,查看、过滤镜像
- ·docker image ls`==·docker images`
- 不同标签的镜像可能image ID是相同的,imageID相同说明是同一个镜像,但其拥有多个标签。
- `docker images --help· || `docker image --help`
- `docker images -a` // 显示所有镜像(默认是不显示中间层镜像的,-a参数可以显示中间层镜像和“悬空”镜像)。
- `docker images --digests [SomeImage]`// --digest参数可以查看(某个)镜像摘要。镜像摘要是基于镜像内容的散列值。
- 当两个镜像标签相同但内容不同时,可以通过摘要值区分这两个不同的镜像。
- `docker images -q` // -q 参数:只列出image ID,该参数在管道命令中很有用。
- `docker images -f key=value` // -f / --filter 参数用于过滤docker images的结果,这里的-f不是强制的意思,而是filter的意思。
- `docker images --filter dangling=true` // 显示所有"悬空"的镜像。
- "悬空"的镜像没有对应的名称和tag,并且其最上层不会被任何镜像所依赖。“悬空”镜像产生的原因是: 构建了一个新的镜像,然后为这个镜像打了一个已经存在的标签,此时Docker会移除旧镜像上的标签,将该标签标在新的镜像之上,如此旧镜像变成了“悬空”的镜像。
- ·docker images --filter before=<imageId or imageName>` // 过滤出(本地的)在所给镜像之前创建的所有镜像
- `docker images --filter since=<imageId or imageName>` //过滤出(本地的)在所给镜像之后创建的所有镜像。
- ·docker images -filter label=<labelId or labelName>` // 过滤出所给label的所有镜像。label不是tag,具体是啥有待研究。
- ·docker images --filter reference="*:latest"`// 过滤出tag为latest的所有镜像。
- ·docker images --format xxx` // --format string Pretty-print images using a Go template。
- `docker images --format "{{.Size}}"` // 注意Size大小写敏感,前面有个点号。只显示仓库的大小。
- ·docker images --format "{{.Repository}} : {{.Tag}} ----- {{.ID}} ==== {{.Size}} "` // 注意ID全是大写。
- ``
※,删除镜像
如果某个镜像层被多个镜像共享,那么只有当全部依赖该镜像层的镜像都被删除后,该镜像层才会被删除!
如果被删除的镜像上存在关联的容器,并且容器处于运行(Up)或者停止(Exited)状态时,那么删除操作不会被允许。但是可以通过 -f 参数强制删除!
- `docker image rm xxx` == `docker rmi xxx` // 区分 ·docker container rm xxx` == `docker rm xxx`
- `docker images -f dangling=true -q | xargs docker rmi` //一般来说,"悬挂"的镜像并不是我们需要的,并且会浪费磁盘空间,可以使用此条管道命令删除所有的“悬挂”镜像。
- ·docker rmi $(docker images -f dangling=true -q)` // 同上条命令
- `docker rmi $(docker images -aq)` // 删除所有镜像,无法删除的会报错
- `docker image prune` //移除全部“悬空”镜像。
- ·docker image prune -a` // -a参数会移除所有没有被容器使用的镜像(包括“dangling”镜像)。
- ·docker images | grep "registry-center" | awk '{print $1":"$2}' | xargs docker rmi· //按名称删除镜像
※,创建一个镜像(参考下文 应用容器化)
※,打包 / 加载 镜像到归档文件(tar或 tar.gz等)
- docker commit --help
- `docker commit <containerName OR containerId> <imageName:Tag>` // 运行的容器生成一个镜像
- ·docker save ubuntu:latest -o ubuntu.tar` // 将镜像 ubuntu:latest 打包成 ubuntu.tar
- ·docker save ubuntu:latest > ubuntu.tar` // 同上
- ·docker load --input ubuntu.tar` // 从归档文件ubuntu.tar中加载镜像,加载后docker images变可以查看到
- ·docker load < ubunut.tar` 同上。
※,
※,docker 容器介绍 ·docker container --help`
※,与虚拟机运行在完整的操作系统之上相比,容器会共享其所在主机的操作系统/内核。
※,启动一个新的容器、进入容器内部
·docker container run ...· == ·docker run ...· https://docs.docker.com/engine/reference/commandline/run/
基础格式:·docker run [OPTIONS] IMAGE [COMMAND] [ARG...]· //启动一个容器并在容器中运行COMMAND命令。如果不指定COMMAND,则会运行镜像的默认命令,这个默认命令可以通过·docker image inspect <imageId or imageName>·查看,有一项叫Cmd,这里配置了这个镜像默认的命令。
Docker容器随着其中运行应用[命令]的退出而终止!容器如果不运行任何进程则无法存在;对于windows容器来说则是杀死容器中的主进程则容器也会被杀死。同时按下 Ctrl + PQ 组合键可以在退出容器的同时还保持容器运行(不会杀死容器中的进程)。
1. Linux启动容器: ·docker run --name myContainer -it ubuntu:latest /bin/bash·
WIndows启动容器: `docker run -it ubuntu:latest pwsh.exe·
- docker run 告诉 docker daemon启动新的容器,
- --name myContainer: 给即将运行的容器指定一个名称。
- -it参数告诉docker开启容器的交互模式并将当前的shell连接到(或者说切换到) 容器终端(即进入容器内部)。
- ubuntu:latest即告诉docker基于此镜像启动容器。如果本地没有此镜像,docker daemon会从仓库托管平台拉取此镜像!
- /bin/bash 或 pwsh.exe 则是告诉docker在容器内部运行这个进程。(注:开始使用的hello-world:latest 镜像测试docker run,一直报错,原因是因为hello-world镜像中不含有/bin/bash二进制文件!)。
- 在容器内部运行ps命令,可以看到两个进程:/bin/bash 和 ps;其中ps在输出后已经退出了,所以实际只有/bin/bash命令在运行。
2、 ·docker [ container ] exec -it <containerId or containerName> bash`
- -it 参数将本地shell连接到容器内部;
- bash 表示告诉docker运行Bash Shell
- 按下CTRL+PQ退出容器后,可以使用docker exec命令重新进入容器内部。使用ps命令查看可以看到两个bash进程,这是因为docker exec 命令创建了新的Bash进程。这意味着在当前Shell输入exit并不会导致容器终止,因为原Bash进程还在运行当中。自己试了下,通过docker exec 命令进入容器内部,即使当前只有一个bash进程,使用exit退出后容器依然不会终止,甚至在容器内是用kill -9 杀死唯一的shell进程(这时会退出容器),容器依然没有终止.
3.`docker run -d --name web1 --publish 8080:8080 test:latest` //没有指定默认运行程序
- -d, --detach: Run container in background and print container ID// -d/--detach 后台模式,让容器中的应用程序以守护线程的方式在后台运行。
- 自测时alpine,ubuntu等镜像在-d参数下运行sh无法成功,不知什么原因!可能是bash,sh等shell程序无法在后台运行?
- ·docker run -dit --name myContainer ubuntu` //-dit 参数组合使用,可以保持STDIN 打开,同时在让容器中的应用在后台运行。 alpine、ubuntu镜像就可以使用-dit参数启动容器同时而不必进入容器内部。
- --name string:Assign a name to the container
- -p, --publish list:Publish a container's port(s) to the host// 端口映射
- somePort //只有一个参数,则表示将容器的此somePort端口映射到宿主机的随机端口上。
- hostPort:containerPort
- hostIp:hostPort:containerPort
- hostIp::containerPort // 容器的containerPort映射到host主机上随机端口上。 本地主机会自动分配一个端口。
- -P, 将Dockerfile中EXPOSE指令声明的需要暴露的所有端口随机映射到宿主机的端口上(Publish all exposed ports to random ports)
4. 关于 -i -t -d三个参数踩到的坑:参考此文
- -i: Keep STDIN open even if not attached,开启标准输入,即使未与容器连接,也维持标准输入。
- -t: Allocate a pseudo-tty,分配一个伪终端。使用TTY模式(pseudo-TTY)若要使用bash,则必须设置该选项。
- -d, --detach: Run container in background and print container ID// -d/--detach 以后台模式运行命令,让容器中的应用程序以守护线程的方式在后台运行。
在运行eqs-service时只使用了-d参数,发现容器启动后立即就停止了,也没有任何错误日志产生。后来发现有些容器-d参数必须配合-i或-t参数一起使用才可以(像busybox就得-itd配合使用,但好像也有些容器只需要-d参数就能运行,这背后的原因还待深挖!)。原因是eqs-service的Dockerfile文件中指定了默认运行命令,此命令需要在/bin/bash中运行。当只指定了-d参数时,/bin/bash找不到任何的伪终端来运行命令,所以容器立即就停止了。而-t参数可以为容器分配一个伪终端。
※,容器的自我修复(重启策略) [官方文档]
容器的重启策略应用于每个容器,可以作为参数被强制传入 docker [ container ] run 命令中,也可以在Compose文件中声明(在使用Docker Compose 以及Docker Stacks的情况下).
目前容器支持的重启策略包括:always、unlsess-stopped 和 on-failure。
Flag Description no
Do not automatically restart the container. (the default) on-failure[:max-retries]
Restart the container if it exits due to an error, which manifests as a non-zero exit code. Optionally, limit the number of times the Docker daemon attempts to restart the container using the :max-retries
option.always
Always restart the container if it stops. If it is manually stopped, it is restarted only when Docker daemon restarts or the container itself is manually restarted. (See the second bullet listed in restart policy details)。
正常手动停止不会重启container,在docker daemon重启后会拉起正常手动停止的container。
unless-stopped
Similar to
always
, except that when the container is stopped (manually or otherwise), it is not restarted even after Docker daemon restarts.正常手动停止不会重启container,在docker daemon重启后也不会拉起正常手动停止的container。
·docker run --name alwaysTest -it --restart=always alpine sh` // 其他两种用法类似。
- always: 除非容器被明确停止,如通过docker stop停止,否则该策略会一直尝试重启处于停止状态(Exited)的容器。当docker daemon重启的时候,则即使是被明确停止的容器也会被重启。
- unless-stopped: 除非容器被明确停止,否则该策略也会一直尝试重启处于停止状态而容器。但是当docker daemon重启的时候,此策略不会重启处于停止状态的容器。
- on-failure: 该策略会在容器退出且容器返回值不是0的时候重启容器。就算容器处于停止状态,在docker daemon重启的时候,容器也会被重启。
5、常用的容器启动方式(主要是记录下参数):
- dubbo-admin: `docker run -itd --name dubbo-admin -p 8880:8080 -e DUBBO_REGISTRY="zookeeper:\/\/192.168.8.222:2181" -e DUBBO_ROOT_PASSWORD=root -e DUBBO_GUEST_PASSWORD=guest --restart=always riveryang/dubbo-admin`
- zookeeper: `docker run -d --name zk -p 2181:2181 --restart=on-failure:10 docker.io/zookeeper`
※,查看容器
·docker container ls` == `docker ps` //查看系统内处于运行状态的全部容器。
- `docker ps -a` // -a参数可以列出全部容器,包括停止状态的。
- `docker ps --format "table {{.ID}}\t{{.Mounts}}\t{{.Names}}"` //--format可以只过滤出需要的字段
可用的占位符
名称 含义 .ID 容器ID .Image 镜像ID .Command 执行的命令 .CreatedAt 容器创建时间 .RunningFor 运行时长 .Ports 暴露的端口 .Status 容器状态 .Names 容器名称 .Label 分配给容器的所有标签 .Mounts 容器挂载的卷 .Networks 容器所用的网络名称 - ·docker ps -f name=xxx` // -f, --filter key=value 参数可以添加过滤条件,可以过滤的项参考官网:https://docs.docker.com/engine/reference/commandline/ps/#filtering
- `docker ps -f name=harbor*`//可以使用通配符
- ·docker ps -f name=harbor* -f status=exited·//多个过滤条件就传多个-f, 状态有如下:
created
,restarting
,running
,removing
,paused
,exited
, ordead
※,查看容器运行时的信息
- ·docker [ container ] inspect <containerId or containerName>` // 显示容器的配置信息和运行时信息。注意一个 `docker image inspect `一个`docker [ container ] inspect `
- `docker inspect <container> --format "{{.GraphDriver.Data.UpperDir}}"` //可以查看容器存储文件的位置(非持久化的数据存储位置)。在容器中的文件随着容器的删除(不是停止)而删除。注意这个文件夹只能用root权限进入!
- `docker inspect <container>` // 输出中的Mounts节点可以查看此容器挂载目录的信息(持久化的数据存储位置):source位置是宿主机位置,destination是容器中的位置。
- ·docker inspect <container> --format "{{.Mounts}}"· // 只查看Mounts节点输出。
- 组合命令查看指定关键词出现的上下若干行:
- ·docker inspect <container> | grep -n <keyword>` // grep 的-n参数可以显示行号。
- ·docker inspect <container> | sed -n '133,155p'· // sed -n '133,155p'表示显示133行到155行
※,暂停/恢复 容器中的服务(作用是啥?)
- ·docker [ container ] pause <containerId or containerName>`
- ·docker [ container ] unpause <containerId or containerName>`
※,停止容器
- ·docker container(可省略) stop <containerId or containerName>`// 多个容器用空格分开
※,删除容器
删除容器推荐分两步走:先停止容器再删除容器。这样可以给容器内的进程机会来有序处理停止前要做的清理工作。可以用Linux/POSIX信号解释:docker stop命令向容器内的进程发送SIGNTERM信号,如果进程10s内没有终止就会收到SIGNKILL信号强制终止进程,一击致命! 而docker rm -f 命令则直接向容器内的进程发送SIGNKILL信号,这会让容器内的进程猝不及防,来不及处理“后事”。
- `docker container(可省) rm <containerId or containerName>`
- ·docker rm $(docker ps -qa)` //删除所有容器,由于正在运行中的容器不会被删除,所以此命令会删除所有处于停止状态的容器。
- ·docker rm <containerId or containerName> -f` //强制删除某个容器,即使此容器正在运行也会被强制删除。
- ·docker container(不可省) prune` // 移除所有停止的容器。
※,查看端口映射信息
- ·docker port <containerId>· // 完整命令是`docker container port <containerId>`
※,重启所有容器快捷命令
- ·docker restart $(docker ps -q)·
※,应用的容器化(创建一个包含当前应用的镜像)
※,Dockfile文件分析(只能是Dockerfile,dockerfile或Docker file都不行)
在Docker中,包含应用文件的的目录叫做构建上下文(Build Context)。通常将Dockerfile放到构建上下文的根目录下。
- Dockerfile中的注释行以#开始,除注释行外每一行都是一条指令。指令参数格式:INSTRUCTION argument。指令是不区分大小写的,但是通常都采用大写的方式,以增加Dockerfile的可读性。
- docker image build 命令会按行来解析Dockerfile中的指令并顺序执行。部分指令会在镜像中创建新的镜像层,其他指令只会增加或修改镜像的元数据信息。
- 新增镜像层的指令包括FROM, RUN, COPY等。新增元数据的指令包括:EXPOSE, WORKDIR, ENV,ENTRYPOINT等。区分是否会新建镜像层的一个基本原则是:如果指令的作用是向镜像中添加新的文件或称程序,那么这条指令就会新建镜像层;如果只是告诉Docker如何完成构建或者如何运行程序,那么就只会增加镜像的元数据。·docker [ image ] history imageName:tag`可以查看构建的过程,size不为0的即是新增镜像层的指令。
- 解析一个示例Dockerfile:
# 每个Dockerfile文件的第一行都是FROM指令。 # FROM指令指定的镜像会作为当前镜像的基础镜像层, # 当前应用的剩余内容会作为新增镜像层添加到基础镜像层之上。 # Linux x64 FROM alpine # FROM指令用于指定要构建镜像的基础镜像。以alpine镜像作为当前镜像的基础镜像。 LABEL maintainer="nigelpoulton@hotmail.com" # 指定维护者,每个LABEL是一个键值对。 # Install Node and NPM 安装NodeJs和NPM RUN apk add --update nodejs nodejs-npm #RUN指令用于在镜像中执行命令,每个RUN指令会创建一个新的镜像层 # Copy app to /src COPY . /src #COPY指令用于将文件作为一个新的层添加到镜像中,亦会创建新的层。将应用的代码复制到镜像当中 WORKDIR /src # 指定新的工作目录,类似cd命令 # Install dependencies RUN npm install # 安装依赖包 # 添加环境变量 ENV <key1>=<value1> <key2>=<value2> ... ENV PATH="$PATH:/usr/local/allure-2.20.1/bin" # EXPOSE <端口1> [<端口2>...] # EXPOSE指令用于记录应用所使用的网络端口(即容器中哪些端口应该被映射出来)。expose只是声明(文档说明性质),并不会实际使用, # 实际使用是通过-p参数指定的.但如果使用了-P,则会将Dockerfile中声明的expose的端口随机映射到宿主机的端口上。 # 关于Dockerfile中的EXPOSE指令和docker run中的-p的关系: # The EXPOSE instruction does not actually publish the port. It functions as a type of documentation between the person # who builds the image and the person who runs the container, about which ports are intended to be published. To actually # publish the port when running the container, use the -p flag on docker run to publish and map one or more ports, # or the -P flag to publish all exposed ports and map them to high-order ports. EXPOSE 8080 ENTRYPOINT ["node", "./app.js"] #ENTRYPOINT指令用于指定镜像以容器方式启动后默认运行的程序。将app.js设置为容器默认运行的应用。 #####其他的Dockerfile指令还有:ENV, ONBUILD, HEALTHCHECK, CMD等
※,Dockerfile注意事项:Dockerfile官方文档点此
Docker镜像是由很多镜像层(Layers)组成的(最多127层), Dockerfile 中的每条指定都会创建镜像层,不过只有 RUN, COPY, ADD 会使镜像的体积增加。这个可以通过命令 docker history image_id 来查看每一层的大小。RUN命令的作用是在当前空的镜像层内执行一条命令,倘若执行的命令须要更新磁盘文件。那么全部的更新内容都在存储在当前镜像层中。举例说明:RUN echo DaoCloud命令不涉及文件系统内容的改动,故命令执行完之后当前镜像层的大小为0;RUN wget http://abc.com/def.tar 命令会将压缩包下载至当前文件夹下,因此当前这一层镜像的大小为:对文件系统内容的增量改动部分,即def.tar文件的大小
- (面试题)Dockerfile编写时的注意事项(如何编写最佳的Dockerfile)
- https://icloudnative.io/posts/intro-guide-to-dockerfile-best-practices/
- https://www.ardanlabs.com/blog/2020/02/docker-images-part1-reducing-image-size.html
- 此系列共三篇文章
- 自总结:两个层面,一是减少构建时间,二是减少构建大小
- ========减少构建时间=======
- 尽量可以利用到缓存的镜像层,变化少的镜像层指令在前面,经常变的写在Dockerfile的后面部分。
- ========减少构建大小======= 参考1
- 1 使用合适的基础镜像.
-
基础镜像,推荐使用 Alpine。Alpine 是一个高度精简又包含了基本工具的轻量级 Linux 发行版,基础镜像只有 4.41M,各开发语言和框架都有基于 Alpine 制作的基础镜像,强烈推荐使用它。进阶可以尝试使用scratch和busybox镜像进行基础镜像的构建。 从官方镜像 redis:6(104M) 和 redis:6-alpine(31.5M) 就可以看出 alpine 的镜像只有基于debian镜像的 1/3。
使用 Alpine镜像有个注意点,就是它是基于 muslc的(glibc的替代标准库),这两个库实现了相同的内核接口。 其中 glibc 更常见,速度更快,而 muslic 使用较少的空间,侧重于安全性。 在编译应用程序时,大部分都是针对特定的 libc 进行编译的。如果我们要将它们与另一个 libc 一起使用,则必须重新编译它们。换句话说,基于 Alpine 基础镜像构建容器可能会导致非预期的行为,因为标准 C 库是不一样的。 不过,这种情况比较难碰到,及时碰到也有解决方法。
-
- 2 减少层数。Dockerfile中一个RUN会在镜像上构建一层,减少层数可以减少大小,比如yum install 多个软件包时可以使用&&和反斜杠\将安装写在一个RUN指令中。(负面作用就是可能会减少docker每层的缓存命中率)
- 3 清理无用数据,删除RUN的缓存文件。一次RUN形成新的一层,如果没有在同一层删除,无论文件是否最后删除,都会带到下一层,所以要在每一层清理对应的残留数据,减小镜像大小。
- 基于debian的镜像
# 换国内源,并更新 sed -i “s/deb.debian.org/mirrors.aliyun.com/g” /etc/apt/sources.list && apt update # --no-install-recommends 很有用 apt install -y --no-install-recommends a b c && rm -rf /var/lib/apt/lists/*
- alpine镜像
# 换国内源,并更新 sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories # --no-cache 表示不缓存 apk add --no-cache a b c && rm -rf /var/cache/apk/*
- centos镜像
# 换国内源并更新 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && yum makecache yum install -y a b c && yum clean all
- 4 多阶段构建。多段构建多用于需要编译安装的软件。
- Dockerfile 最佳实践
- 编写.dockerignore文件
一个容器只运行单个应用
基础镜像和生产镜像的标签不要使用latest
设置WORKDIR和CMD
使用ENTRYPOINT,并用exec启动命令(可选)
相比ADD,优先使用COPY
设置默认的环境变量,映射端口和数据卷
使用LABEL设置镜像元数据
添加HEALTHCHECK
- 编写.dockerignore文件
- Dockerfile中的cmd指令和entrypoint指令的区别和关系
- CMD 与 entrypoint 的深度文章
- exec模式即形如 ·CMD ["ps", "-ef"]· 或 ·CMD ["sh", "-c", "echo good"]·,
- shell模式即形如:·CMD ps -ef·
- Dockerfile中的 COPY指令和 ADD指令的区别
- COPY指令只能从执行docker build所在的主机上读取资源并复制到镜像中。而ADD指令还支持通过URL从远程服务器读取资源并复制到镜像中
- ADD命令会对本地归档文件tar包或者压缩格式为 gzip, bzip2 以及 xz 的情况下进行解压。而COPY不会。
- 事实上,当要读取URL远程资源的时候,并不推荐使用ADD指令,而是建议使用RUN指令,在RUN指令中执行wget或curl命令
- `docker build --progress=plain --no-cache -t name:tag .` //新版docker build使用了buildkit, 此参数可以查看RUN后命令的输出!构建Dockerfile时打印RUN命令的输出
- ·DOCKER_BUILDKIT=0 docker build -t nodejs:v1 .· //恢复老版docker build 输出
※,基于Dockerfile构建包含应用的镜像:
在Dockerfile所在的文件夹内执行如下命令可以根据Dockerfile中的指令创建新的镜像:
- ·docker [ image ] build -t test:latest .` // 生成一个 名称为 test:latest 的镜像。 注意最后有一个代表当前文件夹的点号。
- -t, --tag list: Name and optionally a tag in the 'name:tag' format
- 查看docker build 命令的输出内容,可以了解镜像层的大致构建过程:运行临时容器-->在该容器中运行Dockerfile中的指令-->将指令运行结果保存为一个新的镜像-->删除临时容器-->运行新镜像的临时容器-->一直循环直至Dockerfile命令执行完毕。(构建过程中通过查看镜像和容器会发现一些中间镜像的生成和中间容器的运行。)
- -f , --file string 参数可以指定Dockerfile的路径和名称,可以位于任意位置,名称也可以任意。
- 另一个例子:`docker build -t eqs-service:v1.0.25-test eqs-service`。其中Dockerfile位于eqs-service文件夹内,在包含eqs-service的文件夹内执行此命令。
- ·docker build -t mycontainer:mytag . --network=host· // 指定docker build过程中临时运行的容器使用主机网络。遇到过这样一次案例:95服务器重装iptables,导致95docker容器无法访问外网,导致构建失败。指定使用主机网络就可以成功构建。 95容器无法访问外网的问题 是通过重启docker解决的,重启docker后,iptables的FORWARD链多了很多docker相关的规则,这些规则是重启docker后才加进来的。
※,docker build 的原理: 参考此文,很清晰 整理如下
语法:docker build -t <imageName>:<imageTag> -f <path/to/dockerfile> <buildContext>.其中
- -f是可选参数,用于指定Dockerfile的位置,当使用-f参数指定时,Dockerfile名称可以随意,不必非得是Dockerfile。
- <buildContext>是构建上下文,其实就是一个目录。这个目录会被docker客户端打包发送给docker服务端。服务端收到这个包后进行解压,然后根据Dockerfile中的指令进行镜像的分层构建。所以这个<buildContext>一般只包含必要的文件,不然可能会发送大量无用的流量到docker服务端!!!
- Dockerfile中的ADD指令中的文件位置都是相当于<buildContext>的。即 `ADD dirA dirB`或`ADD ./dirA dirB` 这两个指令中的dirA指的都是<buildContext>/dirA,(dirB是镜像中的某个文件夹)。dirA不能超过buildContext的范围,如:ADD ../dirA dirB这种形式就是不合法的,会报错。
- 总结:所以Dockerfile中需要添加到镜像中的文件都需要放在构建上下文目录中!!!
※,推送镜像到仓库托管(注册)中心(默认为dockerhub: hub.docker.com)
`docker push` === `docker image push`
推送Docker时需要以下信息:Registry(镜像仓库注册中心)、Repository(镜像仓库)、Tag(镜像标签),其中Registry和Tag如果不指定,Docker会默认Registry=docker.io,Tag默认为latest。但是Repository没有默认值,docker会从被推送的镜像的REPOSITORY属性中获取。
- `docker login` //登陆dockerhub[ https://hub.docker.com/ ] everest33/y...t...3.
- ·docker [ image ] tag hello-world:latest everest33/hello-world:latest` // 因为需要将镜像push到自己的命名空间下(直接的hello-world仓库属于官方,一般人没有权限),所以需要创建一个带有自己命名空间的新的镜像。docker tag命令可以创建一个原始镜像的 重命名版本,一般用于在镜像名称中①添加docker私服IP,②添加命名空间,③重命名一个新的tag。新旧两个镜像内容相同,名称不同。`docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`
- ·docker [ image ] push everest33/hello-world:latest` // 此命令便会将镜像推送至dockerhub中。自测时发现,使用docker search everst33/hello-world搜不到刚刚推送的镜像,但是dockerhub网页上的确已经存在。过了一天左右的时间发现使用docker search everest33 可以搜到,但是docker search everest33/hello-world依然搜不到。
- 完整的推送命令:·docker push remote-dockerhub.com/group/namespace/<repository>:<tag>· //如 docker push 192.168.195.95:5000/eqs-service:v1.0.25-test
※,生产环境中的多阶段构建:
对于Docker镜像来说,越小越好!但是生成较小的镜像并非易事。
- 不同的Dockerfile的写法会对镜像的大小产生显著的影响。常见的例子是:每一个RUN指令会新增一个镜像层。因此,通过使用&&连接多个指令以及使用反斜杠(\)换行的方法,将多个命令包含在一个RUN指令中 是一种值得提倡的方式。
构建用于生产环境的镜像的过程中,常常会拉取一些构建工具(如maven, npm等等),如果不清理这些构建工具,那么生成的生产环境镜像中就会包含这些不需要的内容,这是不合适的。清理这些内容又是很费时费力的事情。还好Docker提供了一种叫做 多阶段构建(Multi-Stage-Build) 的方式来解决这种问题。
多阶段构建方式 使用一个Dockerfile,其中包含多个FROM指令。每个FROM指令都是一个新的构建阶段(Build Stage),并且可以方便的复制之前阶段构建的构件。一个多阶段构建的Dockerfile示例如下:
FROM node:latest AS storefront
WORKDIR /usr/src/atsea/app/react-app
COPY react-app .
RUN npm install
RUN npm run build
FROM maven:latest AS appserver
WORKDIR /usr/src/atsea
COPY pom.xml .
RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve
COPY . .
RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
FROM java:8-jdk-alpine
RUN adduser -Dh /home/gordon gordon
WORKDIR /static
COPY --from=storefront /usr/src/atsea/app/react-app/build/ .
WORKDIR /app
COPY --from=appserver /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar .
ENTRYPOINT ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD ["--spring.profiles.active=postgres"]
这个Dockerfile中,最终用于生产环境的组件有前端的组件和后端的组件,但是为了构建这两个组件,构建过程中需要拉取node, npm, maven等生产环境不需要的内容,生产环境只需基于java:8-jdk-alpine镜像,然后加入前端组件加上后端jar包组件就行了。一种方式就是构建完之后,手动清理中间件。下面讲解下多阶段构建的的实现方式:
首先这个Dockerfile中含有3个FROM指令,每个FROM指令构成一个单独的构建阶段,各个阶段在内部从0开始编号。不过示例中针对每个阶段都定义了便于理解的名字:
- 阶段0叫做storefront;这个阶段构建出前端的镜像
- 阶段1叫做appserver;这个阶段构建出后端的镜像
- 阶段2叫做production;这个阶段构建出用于生产环境的镜像。
重点看下阶段2中的COPY --from指令,它从之前构建的镜像中仅复制生产环境相关的应用代码,而不复制生产环境不需要的构件,这样便会减少镜像的体积。在Dockerfile所在的文件夹执行:
·docker [ image ] build -t multi:stage . ` //注意最后的点号。
构建完成后可以使用docker images 查看此Dockerfile中的指令拉取和生成的镜像:
第一行显示了在storefront阶段拉取的node:latest镜像,第二行内容为该阶段生成的镜像(通过添加代码,执行npm 安装和构建操作生成的镜像)。这两个镜像都包含许多构建工具,因此体积非常大。第三、四行是在appserver阶段拉取(第三行)和生成(第四行)的镜像。第六行是在stage2/production阶段拉取的镜像,第七行是production阶段生成的multi:stage镜像,这个镜像基于相对精简的java:8-jdk-alpine镜像构建,并且仅添加了用于生产环境的应用程序文件,因此该镜像明显比之前阶段拉取和生成的镜像要小。
多阶段构建 是随Docker 17.05版本新增的一个特性,用于构建精简的生产环境镜像。
※,构建镜像的最佳实践:
1、利用构建缓存
如果在一台docker主机上重复两次相同的构建会发现第二次构建瞬间完成。这是因为第一次构建的内容(如镜像层)能够被缓存下来,被后续的构建过程复用。
docker [image] build 命令会逐行解析Dockerfile中的指令。对每一条指令,Docker都会检查缓存中是否已经有与该指令对应的镜像层。这个检测很严格,只有当当前行以及当前行之前的所有行都一样才算缓存命中(Cache Hit),否则就是缓存未命中(Cache Miss)。例如:检测第二行指令时,只有存在基于第一行中的镜像层同时第二行的指令也完全相同时才命中(加入第二行是复制内容,那么内容如果发生了变化也是缓存未命中)。一旦Docker检查到缓存未命中,那么后续所有步骤便不会再使用缓存。
·docker [image] build --nocache=true ...`// --nocache=true参数可以强制忽略对缓存的使用。
2、合并镜像
·docker [image] build --squash ...`// --squash参数可以创建一个合并的镜像。
合并镜像有利有弊。当镜像中的层数太多时,合并是一个不错的优化方式。但缺点是合并的镜像将无法共享镜像层,这会导致空间利用率降低,而且push和pull操作的镜像体积更大。例如:某个镜像基于alpine镜像创建,不合并的情况下,push到dockerhub中时,只需要push镜像独有的镜像层,alpine镜像层不需要推送至dockerhub。但是如果合并了镜像层,那么整个镜像层都是独有的,镜像的全部字节都需推送至dockerhub中。
3、使用 no-install-recommends
构建Linux镜像时,若使用的是APT包管理器,则应该在执行apt install命令时增加 no-install-recommends 参数。这个参数会确保APT仅安装核心依赖(Depends中定义的)包,而不安装推荐和建议的包,这样能够显著减少不要包的下载数量。
4、不要安装MSI包(windows).
构建windows镜像时,尽量避免使用MSI包管理器。因为其对空间的利用率不高,会大幅增加镜像的体积。
※,卷与持久化数据
※,容器与非持久数据
每个容器都有自己的非持久化存储。非持久化存储自动创建,从属于容器,生命周期与容器相同,即容器创建时会创建非持久化存储,同时删除容器也会删除全部非持久化数据。
在Linux系统中,非持久化存储目录为:/var/lib/docker/<storage-driver>/之下,如今流行的storage-driver 是 overlay2,具体到容器的存储目录可以使用命令查看,参考上面的 docker [ container ] inspect 命令说明。
在Windows系统中,此目录为:C:\ProgramData\Docker\windowsfilter\ 。
※,容器与持久化数据
- Docker 有两个选项让容器在主机上存储文件,以便即使在容器停止后文件也能持久保存:volumes和 bind mounts。
- 分辨 volumes, bind mounts, and
tmpfs
mounts 之间差异的一种简单方法是考虑数据在 Docker 主机上的位置。
Volumes are stored in a part of the host filesystem which is managed by Docker (
/var/lib/docker/volumes/
on Linux). Non-Docker processes should not modify this part of the filesystem. Volumes are the best way to persist data in Docker.Bind mounts may be stored anywhere on the host system. They may even be important system files or directories. Non-Docker processes on the Docker host or a Docker container can modify them at any time.
tmpfs
mounts are stored in the host system’s memory only, and are never written to the host system’s filesystem.如果希望将容器中的数据持久化存储,则需要将数据存储在卷上。卷与容器是解耦的,从而可以独立的创建并管理卷,并且卷并不与任何容器生命周期绑定,这样用户就可以删除一个关联了卷的容器,但是卷并不会被删除。
卷的使用方法大致说来是:用户首先创建Docker卷,然后创建容器,然后将卷挂载到容器上。卷会挂载到容器文件系统的某个目录之下,任何写到该目录下的内容都会 写到卷中。即使容器被删除,卷与其上面的数据仍然存在。假如Docker卷被挂载到了 /code 目录下,任何写入 /code 目录的内容都会被写入卷,容器的其他目录均使用临时的非持久化的本地存储。
创建新卷:
- ·docker volume create <volName>` // 默认情况下,Docker创建新卷时,采用内置的local驱动,恰如其名,本地卷只能被当前Docker主机上的容器使用。使用-d参数可以指定不同的驱动。
查看卷:
- ·docker volume ls`
- `docker volume inspect <volName>`// 输出中的MountPoint属性说明了卷位于Docker主机上的位置,"Mountpoint": "/var/lib/docker/volumes/myVol/_data"
删除卷:以下两种命令都不会删除正在被容器或服务使用的卷。
- ·docker volume rm <volumeName>`
- `docker volume prune` // 此命令会删除 未装入到某个容器或服务的所有卷!
- 删除容器后,卷依然存在,说明 容器和卷 是独立的。
使用卷(挂载卷后,容器中和宿主机中的数据会互相同步!)
- 貌似在运行中的容器上挂载卷比较困难,可参考:https://www.simapple.com/387.html。
- ·docker [ container ] run -dit --name ubuntu --mount source=bizVol,target=/vol ubuntu`
- --mount 参数在容器的/vol目录上挂载 Docker卷 bizVol;容器中vol目录原来没有则会新创建,如果有则直接用。
- 如果bizVol卷已经存在,则直接使用此卷,如果不存在,则Docker会自动创建一个新卷。
- 删除此容器后,bizVol卷依然存在。这个卷可以被其他的容器或服务继续使用。
容器文件系统挂载:
参考https://docs.docker.com/storage/bind-mounts/
Docker有两个参数实现文件系统的挂载, --mount, -v(--volume),两者的区别如下[经测试-v也能挂载卷,-v <volName>:<containerDir>,如-v testVol:/tmp/dataDir]:
1. -v(--volume)参数
·docker run --name $CONTAINER_NAME -it \ -v $PWD/$CONTAINER_NAME/app:/app:rw \ -v $PWD/$CONTAINER_NAME/data:/data:ro \ avocado-cloud:latest /bin/bash· 注释: 命令格式:[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]]
- https://www.cnblogs.com/ivictor/p/4834864.html【关于文件映射的一些细节很详细,赞】
- ①容器目录不能为相对路径,必须是绝对路径。
- ②指定的宿主机目录如果不存在则会自动创建(注意:只会创建目录,如果你想映射一个文件必须手动创建,否则这个文件会被创建为一个目录)
- ③指定的宿主机目录如果是相对路径,则这个相对路径并不是相对当前目录,而是相对docker自定义的一个目录(一般是/var/lib/docker/volumes/),
具体是哪个目录可以通过docker inspect命令,查看容器“Mounts”那一部分可以看到映射到主机的哪个目录。- ④宿主机目录可以省略,即-v只指定一个参数,此时docker会在宿主机上自动生成一个目录。和③的目录类似,可以通过docker inspect命令查看。
- ⑤如果在容器内修改了目录的属主和属组,那么对应的挂载点是否会修改呢?会修改,但是是通过账号的UID对应起来的,而不是通过名字对应的。
- ⑥容器销毁了,在宿主机上新建的挂载目录是否会消失?不会。即便容器销毁了,新建的挂载目录不会消失。
进一步也可验证,如果宿主机目录的属主和属组发生了变化,容器销毁后,宿主机目录的属主和属组不会恢复到挂载之前的状态。- ⑦挂载宿主机已存在目录后,在容器内对其进行操作,报"Permission denied",或启动容器时失败,docker logs 报错:xxx Permission denied.解决方法有两种:
- 关闭selinux:
- `getenforce` // 查看selinux状态
- `setenforce 0` // 设置SELinux 成为permissive模式。临时关闭,不用重启机器。但是重启后设置失效。
- ·setenforce 1· // 设置SELinux 成为enforcing模式。临时开启。不用重启机器。但是重启后设置失效。
- 修改配置文件,需要重启机器:修改·/etc/selinux/config·文件,将·SELINUX=enforcing·改为·SELINUX=disabled·即可关闭。
- `/usr/sbin/sestatus -v` // 查看selinux状态, 如果SELinux status参数为enabled表示此机器已启用selinux,但不代表是enforcing模式。
- 以特权方式启动容器,指定`--privileged=true`参数,如:# ·docker run -it --privileged=true -v /test:/soft centos /bin/bash·
- ⑧实例中的rw为读写,ro为只读
- ⑨映射后,容器内的程序会使用宿主机中的文件而非原来容器内的文件(如果容器里本来有程序启动时用到的配置文件,但是映射之后宿主机中没有,则会报错。所以首先得新建或拷贝出来)。所以如果想使用自己的配置文件,可以将容器内的配置文件路径映射到宿主机上的自己的配置文件。
2. --mount参数更全面,可以实现-v参数的绑定挂载(bind-mount),也可以挂载卷(volume),还可以挂载tmpfs,通过type来区分这三种类型,如果不指定type则默认为volume。
`docker run --name $CONTAINER_NAME -it \ --mount type=bind,source=$PWD/$CONTAINER_NAME/app,destination=/app \ --mount source=${CONTAINER_NAME}-data,destination=/data,readonly \ avocado-cloud:latest /bin/bash` 注释:【注意】type,source,destination三者之间用逗号隔开,而且不能有空格 挂载volume命令格式(不指定type即默认为volume):[type=volume,]source=my-volume,destination=/path/in/container[,...] 创建bind mount命令格式:type=bind,source=/path/on/host,destination=/path/in/container[,...] 如果创建bind mount并指定宿主机source则必须是绝对路径,且路径必须已经存在,否则报错! 示例中readonly表示只读
3. bind-mount(-v参数的绑定挂载或--mount中type为bind的挂载) 和 volume(卷)的差异比较
Source:即宿主机
Destination:即容器内
对比项 bind mount volume Source位置 用户指定 /var/lib/docker/volumes/ Source为空 将dest覆盖为空 保留dest内容 Source非空 覆盖dest内容 覆盖dest内容 Source种类 文件或目录 只能是目录 可移植性 一般(自行维护) 强(docker托管) 宿主直接访问 容易(仅需chown) 受限(需登陆root用户)* *注释:Docker无法简单地通过
sudo chown someuser: -R /var/lib/docker/volumes/somevolume
来将volume的内容开放给主机上的普通用户访问,如果开放更多权限则有安全风险。而这点上Podman的设计就要理想得多,volume存放在$HOME/.local/share/containers/storage/volumes/
路径下,即提供了便捷性,又保障了安全性。无需root权限即可运行容器,这正是Podman的优势之一,实际使用过程中的确受益良多。在集群节点点共享存储:
Docker能够集成外部存储系统,使得集群节点共享外部存储数据变的简单。在集群节点间共享数据需要注意 数据损坏 问题(如:不同节点都向共享存储写入数据等等),需要在应用程序中予以控制。
※,使用Docker Compose 部署应用
略
docker-compose
※,Docker Swarm
※,使用简介:
★,·docker swarm init --advertise-addr 192.168.1.107:2377 --listen-addr 192.168.1.107:2377· // 将当前主机设置为swarm模式,作为管理者。
- 在当前初始化swarm的主机上运行·docker swarm join-token worker· 和 ·docker swarm join-token manager·命令来获取添加新的工作节点和管理节点到swarm的命令和Token(一个主机节点是作为工作节点还是作为管理节点接入完全取决于使用了哪个Token)。然后在其他主机上执行获取到的命令即可加入这个swarm。
- `docker service create --name bbox --replicas 5 busybox sleep 3600` // 在swarm的manager主机节点上执行此命令,创建一个服务。
- ·docker service create --name swarmNginx -p 80:80 --replicas 1 --network uber-net nginx· // uber-net是overlay网络。关于端口映射:
- 默认情况下swarm采用的是入站模式(Ingress Mode),即swarm中的所有节点都开放80端口,即使某些节点上没有服务的副本。没有服务副本的节点上依然可以通过ip:80端口访问nginx欢迎页面,原因是在入站模式下,所有节点都配置有映射,因此会将请求转发给运行有服务副本的节点。
- 还有一种模式叫做主机模式(Host Mode)。即仅在运行有容器副本的节点上开放端口。以主机模式开放服务端口的语法声明如下:·docker service create --name swarmNginx --network uber-net --publish published=80,target=80,mode=host --replicas 1 nginx·
- ·docker service scale swarmNginx=3· // 对服务swarmNginx进行扩容,将容器扩容至3个。缩容也是同样的命令,将3个减为1个即可。
★,
★,
★,
docker swarm
docker node
docker service
※,使用Docker Stack 部署应用
※,Docker安全
※,企业版工具、企业版特性
※,安全客户端和daemon的通信
※,部署自己的Docker仓库服务(Docker 私服):
卍,Docker公司为我们提供的默认的仓库服务是docker.io,其地址是hub.docker.com。这个地址可以在/etc/docker/daemon.json中配置一个国内镜像(但默认的永远是hub.docker.com),见上文叙述。docker pull 和 docker push命令是通过镜像的名称确定是 拉取自/推送至 哪个仓库服务中心。docker tag命令可以复制并重命名一个镜像。
- docker pull busybox // 从官方仓库服务中拉取 busybox镜像。
- docker tag busybox everest33/busybox:mytag //先将镜像重新打个标签,然后docker push推送
- docker push everest33/busybox:mytag //推送至官方仓库的 everest33命名空间下。注意命名空间是自己的账号名称,不是乱写的!
- docker push 如果多次推送相同标签的镜像,经测试远程仓库中的镜像是会被覆盖的。
- docker pull 192.168.195.95:5000/busybox // 从自己搭建的私服中拉取busybox。
- docker push 192.168.195.95:5000/busybox:v1 //推送至docker私服中。
卍,搭建私服有很多方法,最常用的是使用官方的 Registry 镜像来搭建Docker私有仓库,步骤如下:
- docker pull registry // 从官方仓库hub.docker.com 拉取 registry镜像。
- docker run -itd --name registry -v /opt/data/dockerhub/:/var/lib/registry/ -p 5000:5000 --name dockerhub --restart=always registry:latest // 运行registry
- 【注:此条命令不在Docker私服机器上运行】docker push <dockerRegistryIp>:5000/<myImage:tag> // 在另一台装有docker的机器上push镜像到Docker私服所在的机器,如 docker push 192.168.195.95:5000/myBusybox:v1
- 此时可能会收到一个错误信息:http: server gave HTTP response to HTTPS client。这是因为Registry为了安全性考虑,默认是需要https证书支持的。可以通过配置docker的配置文件解决这个问题(经过测试只需要在发出请求的机器上配置即可,docker私服所在的机器并不需要配置),告诉docker,配置的这些地址可以通过http请求而不用https。
$ vim /etc/docker/daemon.json { "registry-mirrors":["https://docker.mirrors.ustc.edu.cn/","https://hub-mirror.c.163.com/"], // 国内镜像加速 "insecure-registries":["192.168.195.95:5000","10.8.0.238:5000"]//私服地址,可以配置多个。 }
- 注意配置后需要docker重载配置文件 systemctl reload docker, 如还不生效可以重启docker:systemctl restart docker。 通过 docker info命令可以查看配置是否生效。
- 如果一直显示 retrying, 则需要给私服容器registry添加权限,即:·docker run -itd --name registry -v /opt/data/dockerhub/:/var/lib/registry/ -p 5000:5000 --name dockerhub --restart=always --privileged=true registry:latest ·
卍,Docker私服仓库认证配置:加强仓库安全,认证后必须认证用户才可以登录进去私服,认证步骤如下
- `mkdir -p /usr/local/registry/certs` // 创建存储证书的文件夹
- ·openssl req -newkey rsa:2048 -nodes -sha256 -keyout /usr/local/registry/certs/dockerRegistry.key -x509 -days 365 -out /usr/local/registry/certs/dockerRegistry.crt· //按照提示配置
- ·mkdir -p /usr/local/registry/auth· // 创建存储密码的文件夹
- ·htpasswd -Bbn root 123456 > /usr/local/registry/auth/htpasswd· //通过htpasswd命令生成账号密码,需要先安装httpd, `yum install httpd`
`docker run -itd --name registry -p 5000:5000 \
-v /root/dockerhubData:/var/lib/registry \
-v /usr/local/registry/certs:/certs \
-v /usr/local/registry/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/dockerRegistry.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/dockerRegistry.key \
--privileged=true \
registry:latest` // 重新启动私服容器。重启后再向私服推送镜像就会无权限,此时需要先登录私服。- ·docker login 192.168.1.107:5000· // 这里的ip地址必须是在 /etc/docker/daemon.json的insecure-registries 配置的IP地址。
- 再次执行`docker push 192.168.1.107:5000/mybusybox` 即可推送成功
- ·docker logout 192.168.1.107:5000· // 退出登录私服
- curl http://localhost:5000/v2 //返回{} 表示私服正常运行。
- curl http://127.0.0.1:5000/v2/_catalog | python -m json.tool//查看Docker私服仓库中的所有镜像,刚开始是空的,返回值为:{"repositories":[]}
- curl localhost:5000/v2/myBusybox/tags/list // 查看Docker私服中 myBusybox镜像有哪些标签。
- curl -I -X GET localhost:5000/v2/grafana/manifests/v3.11.82 // 查看某个镜像的digest值
- 删除某个镜像的某个版本
- curl -X DELETE http://registry.test.cn:5000/v2/grafana/manifests/v3.11.82
- curl -X DELETE http://registry.test.cn:5000/v2/grafana/manifests/sha256:2d6ff127dd79779c7a4c0e42975ddec4d7243019946e0a53084c8107a736f9e5
- harbor中有用户名密码时访问api的方法:参考此文 , 涉及知识点basic auth
- curl http://admin:Harbor12345@192.168.67.240:30002/v2/_catalog|python3 -m json.tool
- curl -v --basic -u admin:Harbor12345 http://192.168.67.240:30002/v2/_catalog | python3 -m json.tool
卍,删除docker私服中的镜像(清理磁盘空间): 参考 https://ieevee.com/tech/2017/12/28/docker-registry-gc.html
docker私服的GC过程:官网文档 。registry的GC使用“标记-清理”法。
第一步,标记。registry扫描元数据,元数据能够索引到的blob标记为不能删除。
第二步,清理。registry扫描所有blobs,如果改blob没有被标记,则删除它。
跟JVM老年代的GC是不是很像?
docker私服通过docker官方注册中心的 registry 镜像搭建。私服中的镜像 和 私服所在机器的本地镜像是不一样的,私服所在机器本地的镜像通过 docker images命令可以展示出来,但是docker images无法列出私服中的镜像。清除私服中的镜像和清除私服所在机器的本地镜像也是不同的。docker image rm <imageId>删除的是机器本地的镜像。下面是清除私服镜像的方法:
1. 进入docker私服容器内部: docker exec -it registry sh
2. 查看 /etc/docker/registry/config.yml ,
version: 0.1 log: fields: service: registry storage: delete: enabled: true //这里配置true才能删除 cache: blobdescriptor: inmemory //如果删除不掉可以尝试去除cache字段。 filesystem: rootdirectory: /var/lib/registry http: addr: :5000 headers: X-Content-Type-Options: [nosniff] health: storagedriver: enabled: true interval: 10s threshold: 3
3. 容器内部执行: registry garbage-collect [--dry-run] /path/to/config.yml; --dry-run 只查看不执行。执行过程中发现有个地方卡住了很久,耐心等待即可。清理出150G的空间。
4. 如果无法清理出空间,则先调用私服API接口解除绑定关系再删除。具体操作如下脚本:
#!/bin/bash # Author: Tonghuangshan # 调用私服API接口解除绑定关系脚本 :' 1. 调用私服API将私服中的无用tag删除。 这里虽然删除了,但是实际上硬盘地址还没有释放,是因为docker删除p_w_picpath只是删除的p_w_picpath的元数据信息。层数据并没有删除。还需要进入registry中进行垃圾回收 2. 运行完此脚本后再进入私服内部,运行registry garbage-collect [--dry-run] /etc/docker/registry/config.yml 即可真正删除 ' old_IFS=$IFS # 将IFS定义为 空格 换行符 双引号的组合,空格和双引号需要转义。用以将json转为数组 IFS=\ ,$'\n'\" pjts=$(curl -XGET http://192.168.195.95:5000/v2/_catalog | jq .repositories) pjts_arr=($pjts) # 转为数组 for pjt in ${pjts_arr[@]}; do if [[ $pjt == *service* || $pjt == *app* || $pjt == *job* ]];then tags=$(curl http://192.168.195.95:5000/v2/$pjt/tags/list | jq .tags) tag_arr=($tags) #echo ${#tag_arr[@]} #echo ${!tag_arr[@]} #echo ${tag_arr[@]} for tag in ${tag_arr[*]};do #if [[ $tag == *test* || $tag == *prod* ]];then digest=$(curl --header "Accept:application/vnd.docker.distribution.manifest.v2+json" \ -I -XGET http://192.168.195.95:5000/v2/${pjt}/manifests/${tag} | grep Docker-Content-Digest) digest=${digest#*sha256:} # ${digest/$'\r'/''} 将\r替换为空 curl -I -XDELETE http://192.168.195.95:5000/v2/${pjt}/manifests/sha256:${digest/$'\r'/''} #fi done curl http://192.168.195.95:5000/v2/$pjt/tags/list fi done IFS=$old_IFS
卍,harbor垃圾镜像清理相关:harbor UI管理界面(版本 v2.5.3-797c3536)中有垃圾回收功能。测试了下,往harbor中推送了相同tag的几个镜像,然后镜像中出现几个无tag的dangling镜像。手动执行harbor管理页面中的垃圾清理试了试,有时候可以清理出一些空间。但是很多时候并不能清理出空间,尚不知什么情况!猜测是harbor隔一段时间才会去识别真正的垃圾镜像数据。