sudo docker pull nginx:stable-alpine
sudo docker ps -a | grep nginx
docker logs mongo
docker logs -f mongo
sudo docker run -d -p 80:80 nginx:stable-alpine
sudo docker run --name nginx -p 80:80 -d nginx:stable-alpine
docker run -it mongo:6.0 /bin/bash
docker run -it mongo:6.0 --env MONGO_INITDB_ROOT_USERNAME=root --env MONGO_INITDB_ROOT_PASSWORD=example /usr/local/bin/docker-entrypoint.sh
docker run -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example -v /tmp/123:/data/db -it mongo:6.0
docker run -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example -it mongo:6.0 /usr/local/bin/docker-entrypoint.sh mongod
sudo docker cp nginx:/etc/nginx /home/user/nginx
# 以默认用户进入容器
sudo docker exec -it nginx /bin/bash
# 以 root 用户进入容器
docker exec -u 0 -it mycontainer bash
sudo docker-compose up -d
sudo docker stop nginx
sudo docker rm nginx
# 查看 docker 容器 IP 地址:
➜ docker network ls
NETWORK ID NAME DRIVER SCOPE
47c7305f1b14 bridge bridge local
40f22ad2a862 dev_default bridge local
d89cfb0758ba host host local
4b545fd87f77 none null local
➜ docker inspect 40f22ad2a862
[
{
"Name": "dev_default",
"Id": "40f22ad2a862d9527ff45785c24d79146530f6813169fad2cae8c6a08e39d84a",
"Created": "2019-03-27T03:31:50.937926037Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"a966d3e9f64002e76721ac479941544526d2317085efd7a4b5d112a8f43506e7": {
"Name": "dev",
"EndpointID": "0a0cb521fc5967f379364e329ec0fb66b2594395e7d4b5a96ed81707a17184ca",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
➜ docker network inspect 40f22ad2a862
docker network prune
docker0
网卡虚拟网卡 docker0
其实是一个网桥, 如果想删除它, 只需要按照删除网桥的方法即可。
#ifconfig docker0 down
#brctl delbr docker0
docker0
这个网桥是在启动 Docker Daemon 时创建的, 因此, 这种删除方法并不能根本上删除 docker0
, 下次 daemon 启动 (假设没有指定 -b
参数) 时, 又会自动创建 docker0
网桥。
Docker 通过使用 Linux 桥接提供容器之间的通信, Docker 的网络模式有 4 种:
--net=host
指定。--net=container:NAMEorID
指定。--net=none
指定。--net=bridge
指定, 默认配置host 模式
如果容器使用 host 模式, 那么容器将不会获得一个独立的 Network Namespace, 而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡与配置 IP 等, 而是使用宿主机的 IP 和端口。就和直接跑在宿主机中一样。但是容器的文件系统、进程列表等还是和宿主机隔离的。
container 模式
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace, 而不是和宿主机共享。新创建的容器不会创建自己的网卡与配置 IP, 而是和一个指定的容器共享 IP、端口范围等。同样, 两个容器除了网络方面, 其他方面仍然是隔离的。
none 模式
此模式不同于前两种, Docker 容器有自己的 Network Namespace, 但是, Docker 容器没有任何网络配置。而是需要我们手动给 Docker 容器添加网卡、配置 IP 等。
bridge 模式
此模式是 Docker 默认的网络设置, 此模式会为每一个容器分配 Network Namespace, 并将一个主机上的 Docker 容器连接到一个虚拟网桥上。
运行容器
[root@centos7 ~]# docker run -d -P nginx #-d 启动到后台运行
6135db66a7d7c1237901a79974f88f1079b3d467c14ce83fc46bc6b4eb8b3240
[root@centos7 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6135db66a7d7 nginx "nginx -g 'daemon off" 33 seconds ago Up 31 seconds 0.0.0.0:32769->80/tcp, 0.0.0.0:32768->443/tcp gigantic_meitner
随机一个端口去自动映射 80
参数说明:
docker -P
随机端口映射docker -p
指定端口映射-p hostport:containerport
-p ip:hostport:containerport
实例说明
[root@centos7 ~]# docker run -d -p 81:80 nginx
3ca9f847bebec3684952b0f2c081d31f84b9489de50b635246d9a592cc06d46c
[root@centos7 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3ca9f847bebe nginx "nginx -g 'daemon off" 8 seconds ago Up 6 seconds 443/tcp, 0.0.0.0:81->80/tcp goofy_mcnulty
可以通过指定的端口来访问启动的容器服务。
大家都知道 docker run
可以指定端口映射, 但是容器一旦生成, 就没有一个命令可以直接修改。通常间接的办法是, 保存镜像, 再创建一个新的容器, 在创建时指定新的端口映射。
有没有办法不保存镜像而直接修改已有的这个容器呢? 有。
docker stop d00254ce3af7
)systemctl stop docker
)hostconfig.json
文件中的端口 (原帖有人提到, 如果 config.v2.json
里面也记录了端口, 也要修改)systemctl start docker
)docker start d00254ce3af7
)方法二
获得容器 IP
将 container_name 换成实际环境中的容器名:
docker inspect `container_name` | grep IPAddress
将宿主机的 60000 端口映射到容器的 8080 端口:
iptables -t nat -A DOCKER -p tcp --dport 60000 -j DNAT --to-destination 172.17.0.2:8080
docker 启动命名镜像:
docker run -it --name="dev" debian
docker 进入已经退出的容器:
docker start -a -i 2b3ee5bb38e2
docker 容器内外拷贝文件:
docker cp testtomcat:/usr/local/tomcat/webapps/test/js/test.js /opt
docker cp /opt/test.js testtomcat:/usr/local/tomcat/webapps/test/js
查看日志方法 1
通过 docker logs 容器 ID
可以查看到容器主程序的输出, 尝试通过这个分析一下原因。
另外系统镜像默认启动是 bash, 如果没有衔接输入流, 本身就会马上结束。
查看日志方法 2
docker ps -a
, 然后 docker inspect
对应的容器 id 找到 LogPath
: docker inspect xxx | grep LogPath
, 注意 LogPath
的大小写。然后, more xxx.log
。
摘要
Docker 优点已经说过很多次, 这里不做详述, Docker 现在越来越受到开发人员的青睐, 而且利用 Docker 开发的人也越来越多, 本文来自 Vidar Hokstad 博客, 他是一名 Docker 开发资深人士, 他总结了开发 Docker 容器的 8 种模式。
编者按
Vidar Hokstad 在 Docker 使用方面非常有经验, 尤其在没有数据丢失前提下, 使用 Docker 创建可重复 build 上经验丰富, 在本博客中, 他总结了开发 Docker 容器的 8 种模式。
以下为译文
Docker 现在成了我最喜欢的工具, 在本文中, 我将概述一些在我使用 Docker 过程中反复出现的模式。我不期待它们能给你带来多少惊喜, 但我希望这些能对你有用, 我非常愿意与你交流在使用 Docker 过程中碰到的模式。
我所有 Docker 实验的基础是保持 volume 状态不变, 以便 Docker 容器在没有数据丢失的前提下任意重构。
下面所有的 Dockerfiles 例子都集中在: 创建容器在其本身可以随时更换的地方, 而无需考虑其它。
Docker 鼓励"继承", 这应用也很自然——这是高效使用 Docker 的一个基本方式, 不仅由于它有助于减少建立新容器的时间, Docker 优点多多, 它会 cache 中间步骤, 但也容易在不明确的情况下, 失去分享机会。
很显然, 在将我的各种容器迁移到 Docker 上时, 首先要面对的是多个步骤。
对于多数想要随处部署的项目来说所, 要创建多个容器, 尤其是在这个项目需要长进程, 或者需要特定包的情况, 所以我要运行的容器也变得越来越多。
重要的是为了让 mybase 环境完全自由支配, 我正考虑试图在 Docker 上运行"所有一切"(包括我依赖几个桌面 app)。
所以我很快开始提取我的基本设置到 base 容器。这是我当前的"devbase"Dockerfile:
FROM debian:wheezy
RUN apt-get update
RUN apt-get -y install ruby ruby-dev build-essential git
RUN apt-get install -y libopenssl-ruby libxslt-dev libxml2-dev
# For debugging
RUN apt-get install -y gdb strace
# Set up my user
RUN useradd vidarh -u 1000 -s /bin/bash --no-create-home
RUN gem install -n /usr/bin bundler
RUN gem install -n /usr/bin rake
WORKDIR /home/vidarh/
ENV HOME /home/vidarh
VOLUME ["/home"]
USER vidarh
EXPOSE 8080
这里没有什么需要特别说明的地方——它安装一些需要随时可用的特定工具。这些可能会对大多数人来说是不同的。值得注意的是如果/当你重建一个容器的时候, 你需要指定一个特定的标签来避免意外。
使用默认端口 8080, 因为这是我发布 web app 的端口, 这也是我用这些容器的目的。
它为我添加了一个用户, 并且不会创建一个/ home 目录。我从宿主机绑定挂载了一个共享文件夹/ home, 这就引出了下一个模式。
我所有的 dev 容器与宿主机分享至少一个 volume: / home, 这是为了便于开发。对于许多 app, 在开发模式中, 使用基于 file-system-change 的 code-reloader 运行, 这样一来容器内封装了 OS / distro-level 的依赖, 并在初始环境中帮助验证 app-as-bundled 工作, 而不需要让我每次在代码改变时重启/重建 VM。
至于其他, 我只需要重启(而不是重建)容器来应对代码的更改。
对于 test/staging 和 production 容器, 大多数情况下不通过 volume 共享代码, 转而使用"ADD"来增添代码到 Docker 容器中。
这是我的"homepage"的 dev 容器的 Dockerfile, 例如, 包含我的个人 wiki, 存在于"devbase"容器中的 /home 下, 以下展示了如何使用共享的 base 容器和/home 卷:
FROM vidarh/devbase
WORKDIR /home/vidarh/src/repos/homepage
ENTRYPOINT bin/homepage web
以下是 dev-version 的博客:
FROM vidarh/devbase
WORKDIR /
USER root
# For Graphivz integration
RUN apt-get update
RUN apt-get -y install graphviz xsltproc imagemagick
USER vidarh
WORKDIR /home/vidarh/src/repos/hokstad-com
ENTRYPOINT bundle exec rackup -p 8080
因为他们从一个共享的库中取代码, 并且基于一个共享的 base 容器, 这些容器通常当我添加/修改/删除依赖项时会极其迅速重建。
即便如此也有一些地方是我非常愿意改善, 尽管上面的 base 是轻量级的, 他们大多数在这些容器仍未使用。由于 Docker 使用 copy-on-write 覆盖, 这不会导致一个巨大的开销, 但它仍然意味着我没有做到最小的资源消耗, 或者说最小化 attack 或 error 的几率。
这可能对我们这些喜欢依靠 ssh 写代码的人很有吸引力, 但是对 IDE 人群则小一点。对我来说, 关于以上设置更大的 一个好处, 是它让我在开发应用程序中, 能够将编辑和测试执行代码的工作分离开来。
过去 dev-systems 对我来说一件烦人的事, 是 dev 和 production 依赖项以及开发工具依赖项容易混淆, 很容易产生非法的依赖项。
虽然有很多方法解决这个, 比如通过定期的测试部署, 但我更偏爱下面的解决方案, 因为可以在第一时间防止问题的发生:
我有一个单独的容器包含 Emacs 的 installation 以及其他各种我喜欢的工具, 我仍然试图保持 sparse, 但关键是我的 screen session 可以运行在这个容器中, 再加上我笔记本电脑上的"autossh", 这个连接几乎一直保持, 在那里我可以编辑代码, 并且和我的其他 dev 容器实时共享。如下:
FROM vidarh/devbase
RUN apt-get update
RUN apt-get -y install openssh-server emacs23-nox htop screen
# For debugging
RUN apt-get -y install sudo wget curl telnet tcpdump
# For 32-bit experiments
RUN apt-get -y install gcc-multilib
# Man pages and "most" viewer:
RUN apt-get install -y man most
RUN mkdir /var/run/sshd
ENTRYPOINT /usr/sbin/sshd -D
VOLUME ["/home"]
EXPOSE 22
EXPOSE 8080
结合共享"/home", 已经足够让 ssh 的接入了, 并且被证明能满足我的需要。
我喜欢 Docker 的一个原因, 是它可以让我在不同的环境中测试我的代码。例如, 当我升级 Ruby 编译器到 1.9 时, 我可以生成一个 Dockerfile, 派生一个 1.8 的环境。
FROM vidarh/devbase
RUN apt-get update
RUN apt-get -y install ruby1.8 git ruby1.8-dev
当然你可以用 rbenv 等达到类似的效果。但我总是觉得这些工具很讨厌, 因为我喜欢尽可能多地用 distro-packages 部署, 不仅仅是因为如果这项工作的顺利进行, 能使其他人更容易地使用我的代码。
当拥有一个 Docker 容器, 我需要一个不同的环境时, 我仅需要"docker run"一下, 几分钟便能很好的解决这个问题。
当然, 我也可以使用虚拟机来达到目的, 但使用 Docker 更省时间。
这些天我写的代码大部分都是解释性语言, 但还是有一些代价高昂的"build"步骤, 我并不愿意每次都去执行它们。
一个例子是为 Ruby 应用程序运行"bundler"。Bundler 为 Rubygems 更新被缓存的依赖, 并且需要时间来运行一个更大的 app。
经常需要在应用程序运行时不必要的依赖项。例如安装依赖本地扩展 gems 的通常还需要很多包——通常没有记录——通过添加所有 build-essential 和它的依赖项就轻松启动。同时, 你可以预先让 bundler 做所有的工作, 我真的不想在主机环境中运行它, 因为这可能与我部署的容器不兼容。
一个解决方案是创建一个 build 容器。如果依赖项不同的话, 你可以创建分别的 Dockerfile, 或者你可以重用主 app Dockerfile 以及重写命令运行你所需的 build commands。Dockerfile 如下:
FROM myapp
RUN apt-get update
RUN apt-get install -y build-essential [assorted dev packages for libraries]
VOLUME ["/build"]
WORKDIR /build
CMD ["bundler", "install","--path","vendor","--standalone"]
然后每当有依赖更新时, 都可以运行上面的代码, 同时将 build/source 目录挂载在容器的"/build"路径下。
这不是我擅长的, 但是真的值得提及。优秀的 nsenter 和 docker-enter 工具在安装时有一个选项, 对于现在流行的 curl | bash 模式是一个很大的进步, 它通过提供一个 Docker 容器实现"Build Container"模式。
这是 Dockerfile 的最后部分, 下载并构建一个 nsenter 的合适版本:
ADD installer /installer
CMD /installer
"installer"如下:
#!/bin/sh
if mountpoint -q /target; then
echo "Installing nsenter to /target"
cp /nsenter /target
echo "Installing docker-enter to /target"
cp /docker-enter /target
else
echo "/target is not a mountpoint."
echo "You can either:"
echo "- re-run this container with -v /usr/local/bin:/target"
echo "- extract the nsenter binary (located at /nsenter)"
fi
虽然可能还有恶意攻击者试图利用容器潜在的特权升级问题, 但是 attack surface 至少显著变小。
这种模式能吸引大多数人, 是因为这种模式能避免开发人员在安装脚本时偶尔犯的非常危险的错误。
当我认真对待一个 app, 并且相对快速的准备一个合适的容器来处理数据库等, 对于这些项目, 我觉得难能可贵的是已经有一系列的"基本的"基础设施容器, 只做适当的调整就可以满足我的需求。
当然你也可以通过"docker run"得到"主要的"部分, 在 Docker 索引里有诸多替代品, 但我喜欢首先检查它们, 找出如何处理数据, 然后我将修改版本添加到自己的"library"。
例如 Beanstalkd:
FROM debian:wheezy
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -q update
RUN apt-get -y install build-essential
ADD http://github.com/kr/beanstalkd/archive/v1.9.tar.gz /tmp/
RUN cd /tmp && tar zxvf v1.9.tar.gz
RUN cd /tmp/beanstalkd-1.9/ && make
RUN cp /tmp/beanstalkd-1.9/beanstalkd /usr/local/bin/
EXPOSE 11300
CMD ["/usr/local/bin/beanstalkd","-n"]
许多这些模式专注于开发环境(这意味着有 production 环境有待讨论), 但有一个大类别缺失:
容器其目的是将你的环境组合起来成为一个整体, 这是目前为止对我来说有待进一步研究的领域, 但我将提到一个特殊的例子:
为了轻松地访问我的容器, 我有一个小的 haproxy 容器。我有一个通配符 DNS 条目指向我的主服务器, 和一个 iptable 入口开放访问我的 haproxy 容器。Dockerfile 没什么特别的:
FROM debian:wheezy
ADD wheezy-backports.list /etc/apt/sources.list.d/
RUN apt-get update
RUN apt-get -y install haproxy
ADD haproxy.cfg /etc/haproxy/haproxy.cfg
CMD ["haproxy", "-db", "-f", "/etc/haproxy/haproxy.cfg"]
EXPOSE 80
EXPOSE 443
这里有趣的是 haproxy.cfg
backend test
acl authok http_auth(adminusers)
http-request auth realm Hokstad if !authok
server s1 192.168.0.44:8084
如果我想要特别一点, 我会部署类似 AirBnB’s Synapse, 但这已经超出了我的需求。
在工作时扩大容器的规模, 目的是让部署应用程序简单便捷, 就像我正在过渡到一个完整的面向 Docker 的私有云系统。
原文链接: Eight Docker Development Patterns
$ docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 1.12.6
Storage Driver: overlay2
Backing Filesystem: extfs
... output truncated ...
很多方面都会影响存储驱动的选择, 不过有两点必须记住:
此外, 下面的内容, 也可以提供一些指导意见。
稳定性
为了 Docker 环境更加稳定, 你应该考虑一下一些建议:
Overlay vs Overlay2
OverlayFS 有两种存储驱动, 它们使用了相同的 OverlayFS 技术, 但却有着不同的实现, 在磁盘使用上也并不互相兼容。因为不兼容, 两者之间的切换必须重新创建所有的镜像。overlay 驱动是最原始的 OverlayFS 实现, 并且, 在 Docker1.11 之前是仅有的 OverlayFS 驱动选择。overlay 驱动在 inode 消耗方面有着较明显的限制, 并且会损耗一定的性能。overlay2 驱动解决了这种限制, 不过只能在 Linux kernel 4.0 以上使用它。
AUFS VS Overlay
AUFS 和 Overlay 都是联合文件系统, 但 AUFS 有多层, 而 Overlay 只有两层, 所以在做写时复制操作时, 如果文件比较大且存在比较低的层, 则 AUSF 可能会慢一些。而且 Overlay 并入了 linux kernel mainline, AUFS 没有, 所以可能会比 AUFS 快。但 Overlay 还太年轻, 要谨慎在生产使用。而 AUFS 做为 docker 的第一个存储驱动, 已经有很长的历史, 比较的稳定, 且在大量的生产中实践过, 有较强的社区支持。目前开源的 DC/OS 指定使用 Overlay。
有哪些存储驱动
Technology | Storage driver name |
---|---|
OverlayFS | overlay / overlay2 |
AUFS | aufs |
Btrfs | btrfs |
Device Mapper | devicemapper |
VFS | vfs |
ZFS | zfs |
Docker 管理数据的方式有两种:
数据卷是一个或多个容器专门指定绕过 Union File System 的目录, 为持续性或共享数据提供一些有用的功能:
参数说明:
-v /data
直接将数据目录挂载到容器 /data
目录-v src:dst
将物理机目录挂载到容器目录在 linux 上, 容器日志一般存放在 /var/lib/docker/containers/container_id/
下面, 以 json.log
结尾的文件 (业务日志) 很大。
如果 docker 容器正在运行, 那么使用 rm -rf
方式删除日志后, 通过 df -h
会发现磁盘空间并没有释放。原因是在 Linux 或者 Unix 系统中, 通过 rm -rf
或者文件管理器删除文件, 将会从文件系统的目录结构上解除链接 (unlink)。如果文件是被打开的 (有一个进程正在使用), 那么进程将仍然可以读取该文件, 磁盘空间也一直被占用。正确姿势是 cat /dev/null > *-json.log
, 当然你也可以通过 rm -rf
删除后重启 docker。
我现在的方案是使用 contab 每小时定时执行脚本:
_clean_log.sh
:
#!/bin/sh
cat /dev/null > /var/lib/docker/containers/eacc72be16e5533592ca43b1950efccd259e3159d2661ed83d1fc98db94806c6/*-json.log
0 */1 * * * /var/lib/docker/containers/eacc72be16e5533592ca43b1950efccd259e3159d2661ed83d1fc98db94806c6/_clean_log.sh
设置一个容器服务的日志大小上限
上述方法, 日志文件迟早又会涨回来。要从根本上解决问题, 需要限制容器服务的日志大小上限。这个通过配置容器 docker-compose 的 max-size 选项来实现
nginx:
image: nginx:1.12.1
restart: always
logging:
driver: "json-file"
options:
max-size: "5g"
重启 nginx 容器之后, 其日志文件的大小就被限制在 5GB, 再也不用担心了。
- 全局设置
新建 /etc/docker/daemon.json
, 若有就不用新建了。添加 log-dirver
和 log-opts
参数, 样例如下:
# vim /etc/docker/daemon.json
{
"registry-mirrors": ["http://f613ce8f.m.daocloud.io"],
"log-driver":"json-file",
"log-opts": {"max-size":"500m", "max-file":"3"}
}
max-size=500m
, 意味着一个容器日志大小上限是 500M,max-file=3
, 意味着一个容器有三个日志, 分别是 id+.json
、id+1.json
、id+2.json
。// 重启 docker 守护进程
systemctl daemon-reload
systemctl restart docker
注意: 设置的日志大小, 只对新建的容器有效。
- docker volume
像 docker-compose.yml
里面如果使用了下面这样的:
volumes:
elasticsearch-data:
driver: local
那就是使用了 docker 的 volume, 可以通过命令 sudo docker volume ls
查看, 删除的话用命令 sudo docker volume prune
。
摘要: 用了 Docker, 好处挺多的, 但是有一个不大不小的问题, 它会一不小心占用太多磁盘, 这就意味着我们必须及时清理。
使用 Docker 好处还是不少的:
至少, 上线这一年多来, Docker 一直非常稳定, 没有出什么问题。但是, 它有一个不大不小的问题, 会比较消耗磁盘空间。
如果 Docker 一不小心把磁盘空间全占满了, 你的服务也就算玩完了, 因此所有 Docker 用户都需要对此保持警惕。当然, 大家也不要紧张, 这个问题还是挺好解决的。
在《谁用光了磁盘? Docker System 命令详解》中, 我们详细介绍了 docker system
命令, 它可以用于管理磁盘空间。
docker system df
命令, 类似于 Linux 上的 df
命令, 用于查看 Docker 的磁盘使用情况:
docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 147 36 7.204GB 3.887GB (53%)
Containers 37 10 104.8MB 102.6MB (97%)
Local Volumes 3 3 1.421GB 0B (0%)
Build Cache 0B 0B
可知, Docker 镜像占用了 7.2GB 磁盘, Docker 容器占用了 104.8MB 磁盘, Docker 数据卷占用了 1.4GB 磁盘。
docker system prune
命令可以用于清理磁盘, 删除关闭的容器、无用的数据卷和网络, 以及 dangling 镜像 (即无 tag 的镜像)。docker system prune -a
命令清理得更加彻底, 可以将没有容器使用 Docker 镜像都删掉。注意, 这两个命令会把你暂时关闭的容器, 以及暂时没有用到的 Docker 镜像都删掉了… 所以使用之前一定要想清楚呐。
执行 docker system prune -a
命令之后, Docker 占用的磁盘空间减少了很多:
docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 10 10 2.271GB 630.7MB (27%)
Containers 10 10 2.211MB 0B (0%)
Local Volumes 3 3 1.421GB 0B (0%)
Build Cache
对于旧版的 Docker(版本 1.13 之前), 是没有 docker system 命令的, 因此需要进行手动清理。这里给出几个常用的命令
删除所有关闭的容器
docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm
删除所有 dangling 镜像 (即无 tag 的镜像):
docker rmi $(docker images | grep "^" | awk "{print $3}")
删除所有 dangling 数据卷 (即无用的 volume):
docker volume rm $(docker volume ls -qf dangling=true)
有一次, 当我使用 1 与 2 提到的方法清理磁盘之后, 发现并没有什么作用, 于是, 我进行了一系列分析。
在 Ubuntu 上, Docker 的所有相关文件, 包括镜像、容器等都保存在 /var/lib/docker/
目录中:
du -hs /var/lib/docker/
97G /var/lib/docker/
Docker 竟然使用了将近 100GB 磁盘, 这也是够了。cd 到 /var/lib/docker
下, 使用 du -h -t 1G -d 2
(-t
筛选超过指定大小的文件夹 -d
检查深度) 命令继续查看, 可以定位到真正占用这么多磁盘的目录:
92G /var/lib/docker/containers/a376aa694b22ee497f6fc9f7d15d943de91c853284f8f105ff5ad6c7ddae7a53
由 docker ps
可知, nginx 容器的 ID 恰好为 a376aa694b22
, 与上面的目录/var/lib/docker/containers/a376aa694b22
的前缀一致:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a376aa694b22 192.168.59.224:5000/nginx:1.12.1 "nginx -g'daemon off" 9 weeks ago Up 10 minutes nginx
因此, nginx 容器竟然占用了 92GB 的磁盘。进一步分析可知, 真正占用磁盘空间的是 nginx 的日志文件。那么这就不难理解了。如果每天的数据请求为百万级别, 那么日志数据自然非常大。
使用 truncate
命令, 可以将 nginx 容器的日志文件 “清零”:
truncate -s 0 /var/lib/docker/containers/a376aa694b22ee497f6fc9f7d15d943de91c853284f8f105ff5ad6c7ddae7a53/*-json.log
当然, 这个命令只是临时有作用, 日志文件迟早又会涨回来。要从根本上解决问题, 需要限制 nginx 容器的日志文件大小。这个可以通过配置日志的 max-size
来实现, 下面是 nginx 容器的 docker-compose 配置文件:
nginx:
image: nginx:1.12.1
restart: always
logging:
driver: "json-file"
options:
max-size: "5g"
重启 nginx 容器之后, 其日志文件的大小就被限制在 5GB, 再也不用担心了~
还 有一次, 当我清理了镜像、容器以及数据卷之后, 发现磁盘空间并没有减少。根据 Docker disk usage 提到过的建议, 我重启了 Docker, 发现磁盘使用率从 83% 降到了 19%。根据高手 指点, 这应该是与内核 3.13 相关的 BUG, 导致 Docker 无法清理一些无用目录:
it’s quite likely that for some reason when those container shutdown, docker couldn’t remove the directory because the shm device was busy. This tends to happen often on 3.13 kernel. You may want to update it to the 4.4 version supported on trusty 14.04.5 LTS.
The reason it disappeared after a restart, is that daemon probably tried and succeeded to clean up left over data from stopped containers.
我查看了一下内核版本, 发现真的是 3.13:
uname -r
3.13.0-86-generic
如果你的内核版本也是 3.13, 而且清理磁盘没能成功, 不妨重启一下 Docker。当然, 这个晚上操作比较靠谱。
/lib/systemd/system
in your terminal and open docker.service
file: vi /lib/systemd/system/docker.service
ExecStart
and adds -H=tcp://0.0.0.0:2375
to make it look like ExecStart=/usr/bin/dockerd -H=fd:// -H=tcp://0.0.0.0:2375
systemctl daemon-reload
sudo service docker restart
curl http://localhost:2375/images/json
问题: failed to create network xxx: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network
在同一套环境中跑了不少个项目都是用 docker-compose 的方式启动的, 致使建立的自定义网络过多出现报错。
查看自定义网络网络:
docker network ls |wc -l
31
这是由于 Docker 默认支持 30
个不一样的自定义 bridge 网络, 若是超过这个限制, 就会提示上面的错误。你能够使用命令 docker network ls
来查看你建立的网络, 而后经过命令 docker network prune
来移除没有使用的网络。
我采用另外一种方式, 将全部的项目加入到同一个自定义网络当中以节省自定义网络的数量
每台机器上执行: docker network create xxx-network
docker-compose 文件中写入以下内容:
version: "3"
services:
app:
build: ./app
networks:
- xxx-network
networks:
xxx-network:
external: true