Docker 常见使用

目录

1. Docker 常见使用

1.1. docker 常用命令

#!/bin/bash

docker image prune -f --filter="dangling=true"
docker container prune -f
docker volume prune -f
docker network prune -f
watch -n 1 docker ps -a
# stop and remove all containers
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
sudo docker pull nginx:stable-alpine
sudo docker ps -a | grep nginx
docker logs mongo
docker logs -f mongo
docker run -d -p 80:80 nginx:stable-alpine
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 exec -it nginx /bin/bash
# 以 root 用户进入容器
docker exec -u 0 -it mycontainer bash
sudo docker cp nginx:/etc/nginx /home/user/nginx
sudo docker-compose up -d
sudo docker stop nginx
sudo docker rm nginx
# docker 镜像跑完后退出
docker run --rm my-docker

run -itd

官方文档: https://docs.docker.com/engine/reference/commandline/run/

选项选项简写说明
–detach-d在后台运行容器, 并且打印容器 id。
–interactive-i即使没有连接, 也要保持标准输入保持打开状态, 一般与 -t 连用。
–tty-t分配一个伪 tty, 一般与 -i 连用。
header 1header 2header 2
row 1 col 1row 1 col 2row 1 col 2

1.2. 网络 / 网卡

1.2.1. 查看 docker 容器 IP 地址

# 查看 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                localdocker network inspect 40f22ad2a862
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": {}
    }
]

1.2.2. 删除多余网卡

docker network prune

1.2.3. 删除 docker0 网卡

虚拟网卡 docker0 其实是一个网桥, 如果想删除它, 只需要按照删除网桥的方法即可。

#ifconfig docker0 down
#brctl delbr docker0

docker0 这个网桥是在启动 Docker Daemon 时创建的, 因此, 这种删除方法并不能根本上删除 docker0, 下次 daemon 启动 (假设没有指定 -b 参数) 时, 又会自动创建 docker0 网桥。

1.2.4. Docker 网络模式

Docker 通过使用 Linux 桥接提供容器之间的通信, Docker 的网络模式有 4 种:

  • host 模式, 使用 --net=host 指定。
  • container 模式, 使用 --net=container:NAMEorID 指定。
  • none 模式, 使用 --net=none 指定。
  • bridge 模式, 使用 --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

可以通过指定的端口来访问启动的容器服务。

1.2.5. 修改 docker 容器端口映射的方法

大家都知道 docker run 可以指定端口映射, 但是容器一旦生成, 就没有一个命令可以直接修改。通常间接的办法是, 保存镜像, 再创建一个新的容器, 在创建时指定新的端口映射。

有没有办法不保存镜像而直接修改已有的这个容器呢? 有。

  • 方法一
  1. 停止容器 (docker stop d00254ce3af7)
  2. 停止 docker 服务 (systemctl stop docker)
  3. 修改这个容器的 hostconfig.json 文件中的端口 (原帖有人提到, 如果 config.v2.json 里面也记录了端口, 也要修改)
  4. 启动 docker 服务 (systemctl start docker)
  5. 启动容器 (docker start d00254ce3af7)
  • 方法二

  • 获得容器 IP

将 container_name 换成实际环境中的容器名:

docker inspect `container_name` | grep IPAddress
  • iptable 转发端口

将宿主机的 60000 端口映射到容器的 8080 端口:

iptables -t nat -A  DOCKER -p tcp --dport 60000 -j DNAT --to-destination 172.17.0.2:8080

1.3. 镜像

docker 启动命名镜像:

docker run -it --name="dev" debian

删除 tag 为 <none> 的无用镜像: docker image prune -f --filter="dangling=true"

1.4. 容器

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
  • 删除所有已退出的容器: docker container prune -f
  • 停止所有容器: docker stop $(docker ps -a -q)
  • 进入已经退出的容器: docker start -a -i 2b3ee5bb38e2

如何设置 Docker 容器的 CPU 和内存使用限制

  1. 概述

在很多情况下, 我们需要限制 docker 主机上资源的使用。

在本教程中, 我们将学习如何设置 docker 容器的内存和 CPU 限制

  1. 通过 docker run 来限制 Docker 容器资源

我们可以使用 docker run 命令直接设置资源限制。这是一个简单的办法。但是, 该限制于只适用于映像的一次特定执行任务

2.1 Memory 内存限制

例如, 让我们将容器可以使用的内存限制为 512mb

docker run -m 512m nginx

我们还可以设置一个软限制或者叫保留, 当 docker 检测到主机内存不足时激活:

docker run -m 512m --memory-reservation=256m nginx

2.2 CPU 限制

默认情况下, 访问主机的 CPU 是无限制的, 我们可以使用 CPUs 参数设置 cpu 限制。例如, 让我们约束容器最多使用两个 cpu:

docker run --cpus=2 nginx

我们还可以指定 CPU 分配的优先级。默认值是 1024, 数字越高优先级越高:

docker run --cpus=2 --cpu-shares=2000 nginx

与内存保留, 当计算能力不足且需要在竞争进程之间进行分配时, CPU 共享起主要作用

  1. 使用 docker-compose 文件来设置容器资源

我们可以使用 docker-compose 文件来实现类似的结果。请注意, 不同版本的 docker-compose 的格式设置上是不同的

3.1 docker swarm Versions 3 以上的版本

让我们给 Nginx 的服务限制是一半的 CPU 和 512m 的内存, 保留四分之一的 CPU 和 128m 的内存。我们需要在我们的服务配置中创建"部署"和"资源"分段:

services:service:image: nginxdeploy:resources:limits:cpus: 0.50memory: 512Mreservations:cpus: 0.25memory: 128M

一般情况下容器的 cpus 使用被限制在一半, 内存为 512MB , 如果服务器资源紧张的时候, cpus 占用变为 1/4, 内存占用被限制为 128MB

如果要使用上述 docker-compose.yaml 部署应用我们需要使用 docker stack 命令 , 部署应用到 swarm 中

docker stack deploy --compose-file docker-compose.yml bael_stack

3.2 docker-compose Version 2 配置方式

在旧版本的 docker-compose 中, 我们可以将资源限制放在与服务的主要属性相同的级别上。它们的名字也略有不同:

service:image: nginxmem_limit: 512mmem_reservation: 128Mcpus: 0.5ports:- "80:80"

要部署上面的 docker-compose.yaml 应用, 我们需要执行:

docker-compose up
  1. 验证容器资源使用
    一但, 我们设置了 Docker 容器的资源使用, 我们可以用 docker stats 命令来检查设置
$ docker statsCONTAINER ID        NAME                                             CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
8ad2f2c17078        bael_stack_service.1.jz2ks49finy61kiq1r12da73k   0.00%               2.578MiB / 512MiB   0.50%               936B / 0B           0B / 0B             2

1.4.1. 容器启动失败

  • 解决方法 1: 查看日志

查看日志方法 1

通过 docker logs 容器 ID 可以查看到容器主程序的输出, 尝试通过这个分析一下原因。

另外系统镜像默认启动是 bash, 如果没有衔接输入流, 本身就会马上结束。

查看日志方法 2

docker ps -a, 然后 docker inspect 对应的容器 id 找到 LogPath: docker inspect xxx | grep LogPath, 注意 LogPath 的大小写。然后, more xxx.log

1.4.2. stopping docker containers by image name

If you know the image:tag exact container version

docker ps -a -q --filter="name=<containerName>"

Since name refers to the container and not the image name, you would need to use the more recent Docker 1.9 filter ancestor.

docker ps -a -q  --filter ancestor=<image-name>

remove those containers, stop returns the containers as well.

So chaining stop and rm will do the job:

docker rm $(docker stop $(docker ps -a -q --filter ancestor=<image-name> --format="{{.ID}}"))

If you know only the image name (not image:tag)

The ancestor option does not support wildcard matching.

Alex proposes a solution, but the one I managed to run, when you have multiple containers running from the same image is (in your ~/.bashrc for instance):

dsi() { docker stop $(docker ps -a | awk -v i="^$1.*" '{if($2~i){print$1}}'); }

Then I just call in my bash session (after sourcing ~/.bashrc):

dsi alpine

And any container running from alpine.*:xxx would stop.

Meaning: any image whose name is starting with alpine.
You might need to tweak the awk -v i="^$1.*" if you want ^$1.* to be more precise.

From there, of course:

drmi() { docker rm $(dsi $1  | tr '\n' ' '); }

And a drmi alpine would stop and remove any alpine:xxx container.

1.5. 经验之谈: 八种 Docker 容器开发模式

摘要

Docker 优点已经说过很多次, 这里不做详述, Docker 现在越来越受到开发人员的青睐, 而且利用 Docker 开发的人也越来越多, 本文来自 Vidar Hokstad 博客, 他是一名 Docker 开发资深人士, 他总结了开发 Docker 容器的 8 种模式。

编者按

Vidar Hokstad 在 Docker 使用方面非常有经验, 尤其在没有数据丢失前提下, 使用 Docker 创建可重复 build 上经验丰富, 在本博客中, 他总结了开发 Docker 容器的 8 种模式。

以下为译文

Docker 现在成了我最喜欢的工具, 在本文中, 我将概述一些在我使用 Docker 过程中反复出现的模式。我不期待它们能给你带来多少惊喜, 但我希望这些能对你有用, 我非常愿意与你交流在使用 Docker 过程中碰到的模式。

我所有 Docker 实验的基础是保持 volume 状态不变, 以便 Docker 容器在没有数据丢失的前提下任意重构。

下面所有的 Dockerfiles 例子都集中在: 创建容器在其本身可以随时更换的地方, 而无需考虑其它。

1.5.1. The Shared Base Container(s)

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, 这就引出了下一个模式。

1.5.2. The Shared Volume Dev Container

我所有的 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 的几率。

1.5.3. The Dev Tools Container

这可能对我们这些喜欢依靠 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 的接入了, 并且被证明能满足我的需要。

1.5.4. The Test In A Different Environment containers

我喜欢 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 更省时间。

1.5.5. The Build Container

这些天我写的代码大部分都是解释性语言, 但还是有一些代价高昂的"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"路径下。

1.5.6. The Installation Container

这不是我擅长的, 但是真的值得提及。优秀的 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 至少显著变小。

这种模式能吸引大多数人, 是因为这种模式能避免开发人员在安装脚本时偶尔犯的非常危险的错误。

1.5.7. The Default-Service-In-A-Box Containers

当我认真对待一个 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 <a href="http://github.com/kr/beanstalkd/archive/v1.9.tar.gz">http://github.com/kr/beanstalkd/archive/v1.9.tar.gz</a> /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"]

1.5.8. The Infrastructure / Glue Containers

许多这些模式专注于开发环境(这意味着有 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

1.6. Docker 存储驱动

1.6.1. 查看 Docker 当前存储驱动

$ 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 ...

1.6.2. 使用哪种存储驱动

很多方面都会影响存储驱动的选择, 不过有两点必须记住:

  • 没有哪种驱动适合所有的用户场景;
  • 存储驱动一直都在提升和改进;

此外, 下面的内容, 也可以提供一些指导意见。

稳定性

为了 Docker 环境更加稳定, 你应该考虑一下一些建议:

  • 使用你 OS 发行版默认的存储驱动。安装 Docker 时, 它会根据你的系统选择默认的存储驱动, 稳定性是它选择的一个主要方面。
  • 遵守 CS Engine compatibility matrix 指定的配置。CS Engine 是 Docker Engine 的商业化版本, 它代码基于开源的 Engine。不过它有一套限制的支持配置, 而这个支持的配置使用最稳定成熟的存储驱动。

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。

有哪些存储驱动

TechnologyStorage driver name
OverlayFSoverlay / overlay2
AUFSaufs
Btrfsbtrfs
Device Mapperdevicemapper
VFSvfs
ZFSzfs

1.6.3. Docker 数据存储

Docker 管理数据的方式有两种:

  • 数据卷
  • 数据卷容器

数据卷是一个或多个容器专门指定绕过 Union File System 的目录, 为持续性或共享数据提供一些有用的功能:

  • 数据卷可以在容器间共享和重用
  • 数据卷数据改变是直接修改的
  • 数据卷数据改变不会被包括在容器中
  • 数据卷是持续性的, 直到没有容器使用它们

参数说明:

  • -v /data 直接将数据目录挂载到容器 /data 目录
  • -v src:dst 将物理机目录挂载到容器目录

1.7. docker 容器日志查看与清理

1.7.1. 找出 docker 容器日志

在 linux 上, 容器日志一般存放在 /var/lib/docker/containers/container_id/ 下面, 以 json.log 结尾的文件 (业务日志) 很大。

1.7.2. 治标: 清理 docker 容器日志

如果 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

1.7.3. 治本: 设置 Docker 容器日志大小

设置一个容器服务的日志大小上限

上述方法, 日志文件迟早又会涨回来。要从根本上解决问题, 需要限制容器服务的日志大小上限。这个通过配置容器 docker-compose 的 max-size 选项来实现

nginx: 
  image: nginx:1.12.1 
  restart: always 
  logging: 
    driver: "json-file" 
    options: 
      max-size: "5g" 

重启 nginx 容器之后, 其日志文件的大小就被限制在 5GB, 再也不用担心了。

  1. 全局设置

新建 /etc/docker/daemon.json, 若有就不用新建了。添加 log-dirverlog-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+.jsonid+1.jsonid+2.json
// 重启 docker 守护进程
systemctl daemon-reload
systemctl restart docker

注意: 设置的日志大小, 只对新建的容器有效。

  1. docker volume

docker-compose.yml 里面如果使用了下面这样的:

volumes:
  elasticsearch-data:
    driver: local

那就是使用了 docker 的 volume, 可以通过命令 sudo docker volume ls 查看, 删除的话用命令 sudo docker volume prune

1.8. 如何清理 Docker 占用的磁盘空间

摘要: 用了 Docker, 好处挺多的, 但是有一个不大不小的问题, 它会一不小心占用太多磁盘, 这就意味着我们必须及时清理。

使用 Docker 好处还是不少的:

  • 所有服务器的配置都非常简单, 只安装了 Docker, 这样新增服务器的时候要简单很多。
  • 可以非常方便地在服务器之间移动各种服务, 下载 Docker 镜像就可以运行, 不需要手动配置运行环境。
  • 开发/测试环境与生产环境严格一致, 不用担心由于环境问题导致部署失败。

至少, 上线这一年多来, Docker 一直非常稳定, 没有出什么问题。但是, 它有一个不大不小的问题, 会比较消耗磁盘空间。

如果 Docker 一不小心把磁盘空间全占满了, 你的服务也就算玩完了, 因此所有 Docker 用户都需要对此保持警惕。当然, 大家也不要紧张, 这个问题还是挺好解决的。

1.8.1. docker system 命令

在《谁用光了磁盘? 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   

1.8.2. 手动清理 Docker 镜像/容器/数据卷

对于旧版的 Docker(版本 1.13 之前), 是没有 docker system 命令的, 因此需要进行手动清理。这里给出几个常用的命令

删除所有关闭的容器

docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm

删除所有 dangling 镜像 (即无 tag 的镜像):

docker rmi $(docker images | grep "^<none>" | awk "{print $3}")

删除所有 dangling 数据卷 (即无用的 volume):

docker volume rm $(docker volume ls -qf dangling=true)

1.8.3. 限制容器的日志大小

有一次, 当我使用 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, 再也不用担心了~

1.8.4. 重启 Docker

有一次, 当我清理了镜像、容器以及数据卷之后, 发现磁盘空间并没有减少。根据 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。当然, 这个晚上操作比较靠谱。

1.9. How to enable docker remote API on docker host?

  1. Navigate to /lib/systemd/system in your terminal and open docker.service file: vi /lib/systemd/system/docker.service
  2. Find the line which starts with 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
  3. Save the Modified File
  4. Reload the docker daemon: systemctl daemon-reload
  5. Restart the container: service docker restart
  6. Test if it is working by using this command, if everything is fine below command should return a JSON: curl http://localhost:2375/images/json
  7. To test remotely, use the PC name or IP address of Docker Host

1.10. issues

1.10.1. 解决报错 WARNING: IPv4 forwarding is disabled. Networking will not work.

在宿主机上执行 echo "net.ipv4.ip_forward=1" >>/usr/lib/sysctl.d/00-system.conf

重启 network 和 docker 服务

systemctl restart network && systemctl restart docker

1.10.2. 问题: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

问题: 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

1.11. 如何在 VS Code 中配置、部署和调试 Docker

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云满笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值