开篇先目录
零、GPT解答
2、Docker的性能成本问题:CPU成本、内存成本、网络成本:
Docker的开销相对较小,因为它使用的是操作系统级别的虚拟化,不像虚拟机那样需要为每个实例分配一份操作系统。然而,每个容器都会占用一定的系统资源。
- CPU成本:Docker容器的CPU开销通常很小。但是,如果你在一个主机上运行大量的容器,那么这些容器可能会争夺CPU资源,从而影响性能。
- 内存成本:每个Docker容器都需要一些内存来运行。你可以使用Docker的资源限制功能来限制每个容器的内存使用,以防止它们占用太多的系统内存。
- 网络成本:Docker容器需要一些网络带宽来与外界通信。如果你在一个主机上运行大量的网络密集型容器,那么这些容器可能会争夺网络带宽,从而影响性能。
3、Docker的性能压榨情况: CPU、RAM、TCP/UDP连接句柄数、网络流量限制:
- CPU:可以使用Docker的CPU限制功能来限制容器对CPU的使用,这可以防止容器过度占用CPU资源。Docker还支持CPU pinning,这允许你将容器限制在特定的CPU内核上运行。
- RAM:你可以使用Docker的内存限制功能来限制容器对内存的使用。这可以帮助防止容器过度占用内存资源,从而影响系统的其他部分。
- TCP/UDP连接句柄数:这是由主机操作系统管理的,Docker容器会共享主机的网络堆栈,所以如果你发现连接句柄数有限制,可能需要在主机系统级别进行优化和调整。
- 网络流量限制:可以使用Docker的网络限制功能,如设备请求和设备限制,来限制容器的网络带宽使用。
4、快速构建、部署Docker的方式: Docker有几种快速构建和部署的方法:
- 使用Dockerfile:你可以在Dockerfile中定义你的应用程序和它的依赖,然后使用
docker build
命令来创建镜像。- 使用Docker Compose:这是一个工具,可以用来定义和运行多容器的Docker应用程序。你可以在
docker-compose.yml
文件中定义你的服务,然后使用docker-compose up
命令来启动所有服务。- 使用Kubernetes:这是一个开源的容器编排平台,可以用来管理和扩展运行在容器中的应用程序。虽然学习曲线较陡,但Kubernetes提供了许多高级特性,如服务发现、负载均衡、自动滚动更新等。
一、Docker的最小系统构建的普遍方式
- Alpine Linux——安全的、简单的、小巧(基础镜像仅5MB)的Linux发行版
- scratch ——Docker中的一个特殊镜像,它被定义为一个空的(或最小的)基础镜像。它基本上不包含任何文件,也不包含任何操作系统层面的配置。这就意味着你无法在scratch镜像中使用像 bash,ls 等命令,因为这个镜像中并没有这些命令的二进制文件。需要注意的是,使用 scratch镜像需要你的应用能够静态编译,并且能够直接在容器中运行,而不需要依赖其他的系统库或者环境(即无需其他依赖的可执行文件,在Linux就是bin,在Windows就是exe)。这对于某些编程语言(比如Go)来说很容易做到,但对于其他的一些语言则可能比较困难。
总结
- 如果需要使用构建最小系统,单单运行二进制文件,那么可以使用 scratch
- 如果你是需要其他依赖的,比如jre、npm等外部依赖环境,还是使用现有编译好的父镜像来创建的自己的镜像,如
openjdk、node、alpine linux、ubuntu、Debian
。具体见 https://hub.docker.com 。
二、Docker的性能成本问题:CPU成本、内存成本、网络成本
Docker的开销相对较小,因为它使用的是操作系统级别的虚拟化,不像虚拟机那样需要为每个实例分配一份操作系统。然而,每个容器都会占用一定的系统资源。
- CPU成本:Docker容器的CPU开销通常很小。但是,如果你在一个主机上运行大量的容器,那么这些容器可能会争夺CPU资源,从而影响性能。
- 内存成本:每个Docker容器都需要一些内存来运行。你可以使用Docker的资源限制功能来限制每个容器的内存使用,以防止它们占用太多的系统内存。
- 网络成本:Docker容器需要一些网络带宽来与外界通信。如果你在一个主机上运行大量的网络密集型容器,那么这些容器可能会争夺网络带宽,从而影响性能。
三、Docker的性能压榨情况: CPU、RAM、TCP/UDP连接句柄数、网络流量限制
默认情况下,Docker不会对容器的CPU、内存和网络带宽使用做硬性限制。如果不设置限制,一个容器可以使用宿主机上可用的所有资源。然而,由于Docker使用了Linux内核的CGroups和Namespaces,这意味着每个容器都会在一个被隔离的环境中运行,从而在一定程度上避免了资源的争夺。
- CGroups(ControlGroups)是Linux内核中的一个功能,用于管理和限制进程组的资源使用。它允许管理员将一组进程放在一个cgroup中,并为该组分配特定的资源,例如CPU、内存、磁盘IO等。通过CGroups,管理员可以对不同的进程组设置不同的资源限制,从而实现资源的隔离和控制。
- Namespace是Linux内核提供的另一个功能,用于创建隔离的运行环境。通过使用Namespace,可以隔离进程的文件系统视图、网络栈、用户ID、进程ID等。每个Namespace中的进程只能看到自己所属的Namespace的内容,从而实现了进程之间的隔离。
- Docker利用Linux内核的CGroups和Namespace技术来构建容器。通过使用CGroups,Docker可以对容器中的资源进行限制,避免不同容器之间的资源争夺。例如,可以限制每个容器使用的CPU时间片、内存大小等。通过使用Namespace,Docker可以为每个容器提供独立的运行环境,使得容器中的进程无法看到宿主机或其他容器的进程和资源。
尽管Docker不会默认限制这些资源,但你可以在启动容器时手动设置这些限制:
- 对于CPU,可以使用–cpus参数来限制容器可以使用的CPU核心数,或者使用–cpu-shares参数来在多个容器之间分配CPU时间片。
- 对于内存,可以使用-m或–memory参数来设置容器可以使用的最大内存量。
- 对于网络带宽,Docker本身并不提供直接的限制方式,但你可以使用tc工具来设置网络带宽的限制。
默认情况下,Docker容器内的TCP/UDP连接的句柄数没有额外的限制,其上限主要取决于宿主机的系统级别设置。
在Linux系统中,TCP/UDP句柄数限制主要受两个因素影响:文件描述符限制和系统范围的网络连接数限制。
- 文件描述符限制:这是对每个进程打开的文件描述符数量的限制,包括网络套接字。在容器内部,这个值可以通过ulimit命令查看或更改,通常其默认值从宿主机继承。
- 系统范围的网络连接数限制:Linux系统还会对整个系统范围内的总网络连接数有一定限制。这个限制是由内核参数
net.ipv4.netfilter.ip_conntrack_max
等控制的。
所以,如果你在容器中遇到句柄数达到上限的情况,需要同时检查并可能调整这两个地方的设置。如果需要更高的并发连接数,可以考虑提高宿主机和容器的这两个限制。需要注意的是,这些设置可能会对系统的稳定性和性能有影响,因此在调整时需要谨慎,并根据实际情况进行测试。
总结
- Docker容器默认不限制CPU、Memory、网络带宽,由于其使用了Linux内核的Cgroups和Namespace技术,故一定程度上隔离了容器间的资源争夺;
- 对于特定的需求,你可以自定义容器的资源占用限制,命令如下所示:
docker run --cpus CPU核数 --memory 2GB(最大内存使用量) imageName args....
- 对于网络的限制,你可以使用Linux自带的工具类 TC,具体使用方法见 man tc。。
- 默认情况下,Docker容器内的TCP/UDP连接的句柄数没有额外的限制,其上限主要取决于宿主机的系统级别设置。想要提高系统并发性能,参考如下配置:
Linux操作系统内核参数
# 系统全局允许分配的最大文件句柄数。 提高这个值可以允许系统同时打开更多的文件和网络连接。
# 2 millions system-wide
sudo sysctl -w fs.file-max=2097152
sudo sysctl -w fs.nr_open=2097152
sudo echo 2097152 > /proc/sys/fs/nr_open
# 允许当前会话 / 进程打开文件句柄数: 提高这个值可以让单个进程同时打开更多的文件或网络连接。
ulimit -n 1048576
# 持久化 'fs.file-max' 设置到 /etc/sysctl.conf 文件:
fs.file-max = 1048576
# /etc/systemd/system.conf 设置服务最大文件句柄数:
DefaultLimitNOFILE=1048576
# /etc/security/limits.conf 持久化设置允许用户 / 进程打开文件句柄数:
* soft nofile 1048576
* hard nofile 1048576
TCP 协议栈网络参数
# 并发连接 backlog 设置:
# 设置系统监听队列的最大长度。这是等待接受的连接可以排队的最大数量。提高此值可以增加服务器接受新连接的能力。
sudo sysctl -w net.core.somaxconn=32768
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=16384
sudo sysctl -w net.core.netdev_max_backlog=16384
# 可用知名端口范围:
sudo sysctl -w net.ipv4.ip_local_port_range='1000 65535'
# TCP Socket 读写 Buffer 设置:
sudo sysctl -w net.core.rmem_default=262144
sudo sysctl -w net.core.wmem_default=262144
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216
sudo sysctl -w net.core.optmem_max=16777216
#sysctl -w net.ipv4.tcp_mem='16777216 16777216 16777216'
sudo sysctl -w net.ipv4.tcp_rmem='1024 4096 16777216'
sudo sysctl -w net.ipv4.tcp_wmem='1024 4096 16777216'
# TCP 连接追踪设置:
sudo sysctl -w net.nf_conntrack_max=1000000
sudo sysctl -w net.netfilter.nf_conntrack_max=1000000
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
# TIME-WAIT Socket 最大数量、回收与重用设置:
sudo sysctl -w net.ipv4.tcp_max_tw_buckets=1048576
# 注意:不建议开启該设置,NAT 模式下可能引起连接 RST
# sysctl -w net.ipv4.tcp_tw_recycle=1
# sysctl -w net.ipv4.tcp_tw_reuse=1
# FIN-WAIT-2 Socket 超时设置:
sudo sysctl -w net.ipv4.tcp_fin_timeout=15
四、快速构建、部署Docker的方式
Docker有几种快速构建和部署的方法:
- 使用Dockerfile:你可以在Dockerfile中定义你的应用程序和它的依赖,然后使用
docker build
命令来创建镜像。 - 使用Docker Compose:这是一个工具,可以用来定义和运行多容器的Docker应用程序。你可以在
docker-compose.yml
文件中定义你的服务,然后使用docker-compose up
命令来启动所有服务。 - 使用Kubernetes:这是一个开源的容器编排平台,可以用来管理和扩展运行在容器中的应用程序。虽然学习曲线较陡,但Kubernetes提供了许多高级特性,如服务发现、负载均衡、自动滚动更新等。
- 使用可视化容器管理——portainer.io,编排容器更加方便简洁,同时本身支持容器化部署
五、 掌握Java开发时,如何快速构建Docker镜像的方式
引入pom plugin,具体代码参考如下所示:
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker.maven.plugin.version}</version>
<executions>
<!--如果想在项目打包时构建镜像添加-->
<execution>
<id>build-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Docker 远程管理地址-->
<dockerHost>${docker.host}</dockerHost>
<images>
<image>
<!--定义镜像名称-->
<name>${project.name}:${project.version}</name>
<!--定义镜像构建行为-->
<build>
<!--定义基础镜像-->
<from>java:8</from>
<args>
<JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
</args>
<!--定义哪些文件拷贝到容器中-->
<assembly>
<!--定义拷贝到容器的目录-->
<targetDir>/</targetDir>
<!--只拷贝生成的jar包-->
<descriptorRef>artifact</descriptorRef>
</assembly>
<!--定义容器启动命令-->
<entryPoint>["java", "-jar","-Dspring.profiles.active=prod","/${project.build.finalName}.jar"]</entryPoint>
<!--定义维护者-->
<maintainer>KrisYu</maintainer>
</build>
</image>
</images>
</configuration>
</plugin>
具体命令细节,见官方提供的配置手册