文章目录
前言
随着业务的发展,传统的架构已经不符合项目的要求了。双活、集群也渐渐提上了日程。。。
一、就是你了——Docker
最近接到一个需求,生产上要搭建双活环境。再加上现在已有的集群,动辄十几台机子。所有机子都要安装一遍redis、nginx,这工作量简直不可想象。而且各个机子的配置,软件版本存在差异,把测试环境的软件包拿过去安装,发现版本对不上,导致安装失败,简直让人崩溃。经过一番考量后,最后选择docker进行部署,一来可以屏蔽系统之间的差异,不用再在环境上浪费时间。二来搭配自动部署的工具,就算是几十台机子也不用一遍又一遍做重复的工作
二、Docker概览
一说到docker,想必大家脑海里都会浮现一只鲸鱼驮着一堆集装箱的图片,鲸鱼驮着一堆集装箱游走在各个海域。事实也是如此,鲸鱼就是docker,而集装箱,就是我们装的软件,海域则是不同的环境
docker简单来说,可以理解成一个mini版的虚拟机。那为什么不直接用Vmware而是用docker,主要还是因为Vmware太重了,像平时用Vmware装个Linux系统,动辄4C4G起步,装的软件一多还得继续往上砸硬件,显然这成本就太高了。docker就是基于这种情况下出现的,之所以轻量级,是除了软件必虚的环境之外,不安装多余的东西。之所以可移植,是docker屏蔽了环境之间的差异,如果说是java是一次编译,四处运行。那么docker就是一次打包,四处部署(相对而言)。计算机的世界,果然没有什么是加一层不能解决的,如果有,那就再加一层
注:docker貌似没有实现真正意义上的跨平台,还是依赖于操作系统,在部署的过程中,发现Linux打包的镜像无法再Windows部署,不过细想下:docker打包的镜像包含操作系统,Linux下打包的镜像,自然无法导入Windows。貌似也没毛病
三、 Docker架构
了解完docker,接下来再来看下docker的架构,看下docker具体怎么执行的。下边是官方给出的流程图,如下:
可以看出,Docker主要分为:客户端、守护进程、仓库、镜像 这几部分。下边就一一来看下这几个某块分别的作用
守护进程(Docker daemon)
守护进程是docker最核心的部分。负责处理客户端请求,docker资源,如:镜像、容器、网络都是由守护进程在管理
客户端(Docker client)
客户端就比较简单了,像平时敲得命令:docker、docker-compose 都属于客户端。客户端负责向守护进程发起请求
镜像(Images)、容器(Containers)
镜像(Images)、容器(Containers)、网络(networks)、卷(volumes)都属于docker对象的一部分,这里主要说说下镜像和容器
镜像和容器的关系有点类似于类和对象的关系,镜像提供只读模板,容器创建具体内容
仓库(Docker registries)
仓库就比较好理解了,现在一般会部署集群,这时候如果没有仓库,就需要把各个镜像上传到对应的服务器再进行安装,这显然不符合快速部署的要求。所以,一般会把镜像发布在公共仓库上,docker自己去下载即可
那么这几个组件之间是如何配合工作的呢?以docker run
命令为例,大体流程:客户端发起请求,守护进程先查看本地是否存在镜像,不存在则去仓库下载,而后创建出容器。
四、 Docker安装
知道了docker的作用,直接开始撸袖子干。docker安装分为在线、离线。由于云上机子不能联网,所以这里重点聊下离线安装。其他安装方式可以参考官方安装文档,这里有详细说明
离线安装步骤
- 下载tgz包
先去官网下载tgz包,这里选择的是20.10.5版本(根据自己实际需求选择)
- 解压文件
tar -zxvf docker-20.10.5.tgz
- 移动到对应目录
cp docker/* /usr/bin/
- 配置阿里镜像加速
由于docker是外国人的东西,所以仓库默认连的是外网,需要给它提提速。国内加速方式也挺多的,如:百度、阿里、交大等。这里就以阿里云的镜像加速器为例。如下:
步骤写的很清楚,跟着操作就是了,这里使用离线安装,还没有配置系统服务,后面两步先省略。如下:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["加速地址"]
}
EOF
- 启动并验证
现在只需要启动docker即可,命令如下:
nohup dockerd &
docker已经启动完成,接着启动个nginx验证一下。命令如下:
docker run -itd --name nginx -p 8080:80 nginx
通过ip:8080访问,如下:
可以看到,nginx已经启动完毕。回想之前在没有docker的情况下,安装nginx还得自己编译,是不是感觉方便了许多
- 注册系统服务
接着还需要把docker注册为系统服务,方便Linux管理和启停
#写入service文件
tee /etc/systemd/system/docker.service <<- 'EOF'
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
#设置开机自启
systemctl enable docker
#启动docker
systemctl start docker
注:如果启动过程中遇到:Job for docker.service failed because start of the service was attempted too often. 则执行systemctl reset-failed docker.service
即可
如果遇到:Job for docker.service failed because the control process exited with error code.
See “systemctl status docker.service” and “journalctl -xeu docker.service” for details。看下服务配置,如下:
# Ubuntu的路径; CentOS 的路径为: /usr/lib/systemd/system/docker.service
vim /lib/systemd/system/docker.service
#修改文件内容:
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStart=/usr/bin/dockerd
#重新启动docker服务
service dockerd restart
docker.sock 是 Docker 守护进程的通信接口,而 containerd.sock 是 containerd 的通信接口。两者在功能和使用场景上有所不同。docker.sock是一个完整的容器平台,包括了容器运行时、镜像管理、网络和存储等多个方面的功能,而 containerd.sock 则更加专注于容器的生命周期管理。因此,在选择使用哪个套接字时,取决于你的具体需求和应用场景
如果还是不行,那只能重启了
五、搭建Docker私服
云上开发,最不方便的地方就是不能连外网,查资料,下载东西非常麻烦。上边配置的docker仓库,就无法连接上阿里云,这时候就需要搭建docker私服
搭建docker私服有几种方式:registry、nexus、harbor 这里逐一搭建下吧
Registry
registry 是官方提供的镜像仓库。仅能使用api和json进行操作,不够友好,所以一般情况下不用这个,了解即可
- 运行registry
docker run -itd -p 5000:5000 --name registry registry
访问:http://ip:5000/v2/_catalog,不出意外的话,会出现以下界面:
- 推送镜像
在操作之前,需要配置一下/etc/docker/daemon.json文件,具体配置如下:
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["registry-ip:5000"]
}
重启下docker:
systemctl daemon-reload
systemctl restart docker
这样私有仓库就配置完成了
在上传进行之前还需要重命名下镜像 的名称,docker会根据镜像名称选择上传的仓库。如下:
#重命名成registry仓库地址
docker tag nginx 192.168.233.130:5000/nginx
#上传镜像
docker push 192.168.233.130:5000/nginx
再次访问:http://ip:5000/v2/_catalog,出现nginx镜像。如下:
- 拉取镜像
执行下列命令从私服拉取镜像:
docker pull 192.168.233.130:5000/nginx
注:这里拉取的时候需要添加私服地址,这涉及到docker镜像命名的问题,如果直接通过镜像拉取,并不是像Maven那样:先去私服找,再去公网找,即使配置了私服地址也没用,官方貌似拒绝了这个提案。RedHat系统可以添加 --add-registry 和 --block-registry 进行覆盖,其他系统就不行了,github也有相应的讨论
Nexus
现在docker镜像的工具已不少了,之所以带Nexus玩,主要还是因为之前Maven私服也是用Nexus搭建的,这样可以充分利用资源。嘿嘿~~
- 安装Nexus
Nexus还是用docker进行安装,先启动Nexus,命令如下:
docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 -v /etc/localtime:/etc/localtime --name nexus3 sonatype/nexus3
Nexus3默认用户名是admin,密码需要进入容器内部进行查看。如下:
#进入容器内部
docker exec -it nexus3 bash
tail -200f /nexus-data/admin.password
2.创建仓库
现在只需要在Nexus提供的界面上建立docker仓库即可。具体步骤如下:
按照上面配置直接创建仓库即可。创建完成Nexus会多个docker仓库
- 推送镜像
推送镜像和Registry一样,配置一下/etc/docker/daemon.json文件。如下:
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["nexus-ip:8082","registry-ip:5000"]
}
重启下docker:
systemctl daemon-reload
systemctl restart docker
接着需要先登录到Nexus(Nexus存在权限校验)再进行推送。如下:
#登录Nexus
docker login -u admin -p admin nexus-ip:8082
#重命名成nexus私库地址
docker tag nginx 192.168.233.130:8082/nginx
#推送镜像
docker push 192.168.233.130:8082/nginx
- 拉取镜像
拉取镜像和Registry一样了,就不一一赘述了
注:不论是Registry还是Nexus,这里只是简单的示例,无法用于实际环境。如果确定使用docker部署,要把数据持久化到服务器,否则容器终止,数据也会消失
Harbor
接下来欢迎本次的主角——Harbor。以下是它的自我介绍:
Harbor 是一个开源可信云原生注册表项目,用于存储、签名和扫描内容。 Harbor 通过添加用户通常需要的功能(例如安全性、身份和管理)来扩展开源 Docker 发行版。让注册表更接近构建和运行环境可以提高映像传输效率。 Harbor 支持注册表之间的镜像复制,还提供高级安全功能,例如用户管理、访问控制和活动审核
看不懂,反正很厉害就是了。还是先学会怎么用吧
-
下载Harbor
虽然Harbor是用docker部署的,但是分为几个模块,官方对其进行了整合,还是得下载下安装包。先去官方的github整个安装包(根据实际情况选择版本),这里以2.9.1为例 -
解压
tar -zxvf harbor-offline-installer-v2.9.1.tgz
解压后的目录结构,如下:
可以看到Harbor已经准备好了一切
- 安装
在正式安装之前,先改下文件夹名称、添加下配置文件:
mv harbor harbor-install
cd harbor-install
cp harbor.yml.tmpl harbor.yml
再修改下配置文件,打开harbor.yml,参考配置如下:
#主机名,这里改成ip
hostname: 192.168.233.130
http:
#访问端口,根据实际情况配置
port: 8080
# https相关配置,这里不需要,直接注释,否则会报错
#https:
# port: 443
# certificate: /your/certificate/path
# private_key: /your/private/key/path
#登录密码
harbor_admin_password: Harbor12345
# Harbor DB configuration
database:
password: root123
max_idle_conns: 100
max_open_conns: 900
conn_max_lifetime: 5m
conn_max_idle_time: 0
# 数据存储位置
data_volume: /usr/local/docker/harbor/data
trivy:
ignore_unfixed: false
skip_update: false
offline_scan: false
security_check: vuln
insecure: false
jobservice:
max_job_workers: 10
job_loggers:
- STD_OUTPUT
- FILE
logger_sweeper_duration: 1 #days
notification:
webhook_job_max_retry: 3
webhook_job_http_client_timeout: 3 #seconds
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
#日志存储位置
location: /usr/local/docker/harbor/logs
_version: 2.9.0
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy
upload_purging:
enabled: true
age: 168h
interval: 24h
dryrun: false
cache:
enabled: false
expire_hours: 24
配置完成后,直接安装即可:
./install.sh
直接访问ip:8080(端口、密码根据上边配置进行填写)
- 推送镜像
在推送镜像之前,先新增一个项目:
接下来的操作,就和Nexus大同小异了。配置/etc/docker/daemon.json文件。如下:
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["harbor-ip:8080","nexus-ip:8082","registry-ip:5000"]
}
重启docker:
systemctl daemon-reload
systemctl restart docker
登录、推送。如下:
#登录到Harbor
docker login -u admin -p Harbor12345 192.168.233.130:8080
#重命名为Harbor地址
docker tag nginx 192.168.233.130:8080/test/nginx
#推送到Harbor
docker push 192.168.233.130:8080/test/nginx
相较于Nexus,Harbor 路径多了个test,这个即之前创建的项目名
- 拉取镜像
拉取镜像和推送一样,需要带上项目名:
docker pull 192.168.233.130:8080/test/nginx
六、Dockerfile自定义镜像
前边一直操作别人提供好的镜像,在实际工作中,往往都有我们自己的项目,这时候需要自己构建镜像并提交到私库,方便运维人员进行部署
先用Idea创建一个简单的Springboot项目。具体步骤想必大家都懂,这里就不赘述了。重点看下pom.xml文件。如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>docker-demo</artifactId>
<version>1.0</version>
<name>docker-demo</name>
<description>docker测试项目</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>docker-demo</finalName>
<plugins>
<!-- docker插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.2</version>
<configuration>
<!-- 镜像名称,'[a-z0-9-_.]'格式 -->
<imageName>${project.artifactId}:${project.version}</imageName>
<dockerDirectory>${project.basedir}/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
<include>classes/application.*</include>
</resource>
</resources>
<!--这里用tcp,用提示协议错误-->
<dockerHost>http://192.168.233.135:2375</dockerHost>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.dockerdemo.DockerDemoApplication</mainClass>
<skip>false</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
构建镜像需要用到Dockerfile,docker 通过读取 Dockerfile 中的指令来自动构建镜像。通常,一个镜像基于另一个镜像,就像千层饼一样一层套一层。接下来就看下如何把jar文件打包成镜像吧。显然,Jdk镜像是我们的基础镜像。Dockerfile如下:
#引入jdk镜像
FROM openjdk:8-jre-slim
# 作者名
MAINTAINER hqd
#设置中国时区,否则容器内部时间显示美国时间。和实际时间对不上
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ADD docker-demo.jar /app.jar
ADD classes/application.* /config/
#创建具有指定名称的安装点,并将其标记为保存来自本机主机或其他容器的外部安装卷
VOLUME config
#实际上并未发布端口。它充当构建映像的人员和运行容器的人员之间的一种文档,有关要发布哪些端口。使用-p进行覆盖
EXPOSE 8080
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]
接下来,只需要构建镜像即可,构建方式主要有两种:命令行和远程。下边都来看下吧
命令行
命令行构建需要把构建所需的文件放到服务器上(需要docker环境)。目录结构如下:
而后,执行docker命令进行构建,如下:
# .即当前目录,t即tag,为镜像名称
docker build . -t docker-demo:1.0
如果出现异常,会产生仓库名、标签都是none的镜像,即虚悬镜像:
这时候只需执行清理即可,如下:
#清理镜像
docker image prune
#清理系统
docker system prune
远程
现在基本都是用Maven进行开发,我们更希望直接通过Maven进行远程部署,这时候就需要用到DockerMaven插件
- docker开启2375端口
想要远程构建镜像,docker需要先开启端口进行通信,修改 /etc/docker/daemon.json 文件,如下:
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["harbor-ip:8080","nexus-ip:8082","registry-ip:5000"]
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]
}
重启docker:
systemctl daemon-reload
systemctl restart docker
- 项目添加Dockerfile
再把Dockerfile添加到项目中。如下:
- 添加DockerMaven插件
再引用下DockerMaven插件,上面的pom.xml已经给出全部配置,这里主要看下插件部分。如下:
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.2</version>
<configuration>
<!-- 镜像名称,'[a-z0-9-_.]'格式 -->
<imageName>${project.artifactId}:${project.version}</imageName>
<dockerDirectory>${project.basedir}/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
<include>classes/application.*</include>
</resource>
</resources>
<!--这里用tcp,用提示协议错误-->
<dockerHost>http://服务器Ip:2375</dockerHost>
</configuration>
</plugin>
- 构建镜像
点击Idea右侧Maven进行构建。如下:
七、命令繁琐?那就用docker-compose
生产上往往不止一台机子,而部署的应用也有很多。一条条命令去敲也很麻烦,需要对进行容器编排,方便管理。现在比较流行的容器编排是k8s,目前这个项目用不到,这边只用docker-compose即可。至于什么是docker-compose,简单来说:就是把多个容器的运行命令写到YAML文件中去,这样方便统一管理,易于共享
- 下载
老样子,先下载docker-compose,这里用的是v2.24.5(根据自己需求选择)
- 安装
把下载好的文件上传至服务器,执行命令:
#重命名文件
mv docker-compose-linux-x86_64 docker-compose
#添加执行权限
chmod u+x docker-compose
#移动到系统目录
mv docker-compose /usr/bin/
#查看版本
docker-compose -v
- 验证
之前启动镜像都是用命令行直接启动,现在体验一下docker-compose,看看为什么选择它。就以之前提到的docker-demo和nginx为例
docker-demo配置和之前的一样,不做改动。nginx配置文件如下:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name 192.168.233.135;
location / {
proxy_read_timeout 300;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://docker-demo:8080;
}
}
}
注:其他配置大家应该都不陌生,可能会对proxy_pass http://docker-demo:8080感到疑惑,这个涉及到docker网络部分,后边会聊到
编写YAML文件如下:
version: '2' # 表示该 Docker-Compose 文件使用的是 Version 2 file
services:
docker-demo: # 服务名称
image: docker-demo:1.0
container_name: docker-demo #容器名称
# build: /usr/local/docker/docker-demo # 指定 Dockerfile 所在路径
ports: # 指定端口映射
- "8080:8080"
volumes:
- /usr/local/docker/docker-demo/classes:/config
networks:
- docker-demo
nginx: # 服务名称
image: nginx
container_name: nginx #容器名称
ports: # 指定端口映射
- "8081:80"
volumes:
- /usr/local/docker/nginx/logs:/var/log/nginx
- /usr/local/docker/nginx/config/nginx.conf:/etc/nginx/nginx.conf
networks:
- docker-demo
networks:
docker-demo:
driver: bridge #使用桥接创建网络
接着,进去docker-compose文件所在目录,执行启动命令:
#启动所有应用,-d后台启动
docker-compose -f docker-demo.yml up -d
#关闭所有应用
docker-compose -f docker-demo.yml down
#重启所有应用
docker-compose -f docker-demo.yml restart
应用起来了,疑惑也起来。下边就来看下volumes(数据卷) 和 networks(网络)
networks(网络)
玩过Vmware的应该配置过它的网络,以前该接触时,要么主机ping不通虚拟机,要么虚拟机ping不通主机,对此没少百度。现在回头再来看,Vmware的网络大致分为:
- 桥接
桥接模式下,虚拟机和宿主机都会连接到虚拟交换机上,宿主机的网卡与虚拟机的虚拟网卡通过虚拟网桥连接起来,它们的地位“平等”,同处于一个网段互不干拢,虚拟机是网络的完整参与者
-
NAT
NAT模式下,虚拟机在外部网络上没有自己的 IP 地址。相反,在宿主机上设置一个专用网络。其原理是将专用网络中虚拟机的 IP 地址转换为主机系统的 IP 地址。当虚拟机发送访问网络资源的请求时,网络资源看起来就像该请求来自主机系统。这样看来,宿主机像是虚拟机的“上级”
-
仅主机
仅主机模式下,虚拟机和主机虚拟网络适配器连接到专用以太网,虚拟机只能和主机通信,不能与外网进行通信,相当于一个封闭的局域网
跑题了~,回到docker,再看下docker的网络:
-
Bridge
Bridge模式虽然叫Bridge,但是并不等价于Vmware的桥接,而是与NAT模式相似,docker会在宿主机创建一个docker0网卡,该网卡会与一个虚拟交换机相连,当容器以Bridge模式创建启动时,会给容器创建一个虚拟网卡,该网卡分配的IP与宿主机的docker0所在同一个局域网内 -
Host
Host模式会直接使用宿主机的网卡,容器本身并不会创建虚拟网卡,容器暴露的端口可以直接在宿主机中查到,可以当成就是在宿主机真实执行的程序。使用此模式时 -p 参数失效 -
Container
Container模式会指定一个容器的网卡为网卡,当前容器也不会创建虚拟网卡。这两个容器不能有一样的端口暴露 -
None
None模式不会给容器创建网卡,相当于一个没有网卡的机子,无法与外部通讯 -
Overlay
Overlay模式是为了在多个docker守护进程主机之间创建分布式网络,即不同机子上部署的docker可以相互通讯,该模式允许连接到它的容器(包括集群服务容器)安全地通信 -
IPvlan
IPvlan模式比较麻烦,没看懂,直接摘取官网的描述:IPvlan 是经过考验的真实网络虚拟化技术的新变化。 Linux 实现非常轻量级,因为它们不是使用传统的 Linux 桥进行隔离,而是与 Linux 以太网接口或子接口相关联,以强制网络之间的分离以及与物理网络的连接。
IPvlan提供了许多独特的功能,并为各种模式的进一步创新提供了充足的空间。这些方法的两个高级优势是,绕过 Linux 桥的积极性能影响以及移动部件较少的简单性。移除传统上位于 docker 主机 NIC 和容器接口之间的桥接器,留下由直接连接到 docker 主机接口的容器接口组成的简单设置。面向外部的服务很容易访问此结果,因为在这些场景中不需要端口映射
IPvlan L2
- Macvlan
Macvlan模式一样没看懂,官网描述:某些应用程序,尤其是遗留应用程序或监视网络流量的应用程序,期望直接连接到物理网络。在这种情况下,您可以使用Macvlan网络驱动程序为每个容器的虚拟网络接口分配一个MAC地址,使其看起来像是直接连接到物理网络的物理网络接口。在这种情况下,您需要在docker主机上指定用于 Macvlan的物理接口,以及网络的子网和网关。您甚至可以使用不同的物理网络接口隔离 Macvlan 网络
volumes(数据卷)
volumes又是个麻烦的话题。简单来说,是为了方便备份或迁移,以及共享。前文提到过,如果没有volumes(命令行的 -v 或 --mount 、Docker Compose的volumes),一旦容器挂了,数据就会丢失,类似于数据库这样的应用,怕是牢饭管饱。。。
为了避免牢饭管饱,就需要把容器内的数据映射到宿主机上边保存,这也是volumes主要作用
八、Docker监控
用docker启动好各个应用后,还需要对各个应用进行监控,如果哪台服务挂了,方便定位重启。监控工具主要是:
Portainer
官方介绍
是一个针对容器化应用程序的轻量级服务交付平台,可用于管理 Docker、Swarm、Kubernetes 和 ACI 环境。它的设计理念是部署和使用都简单。该应用程序允许您通过“智能”GUI 和/或广泛的 API 管理所有编排器资源(容器、图像、卷、网络等)
巴拉巴拉,只要记住是轻量级的容器管理平台就行了。Portainer分为社区版(CE)和商业版(BE),当然,这里用的是社区版。有了docker部署就很方便:
#创建数据卷
docker volume create portainer_data
#安装中文版
docker run -d -p 9000:9000 --name=portainer -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data outlovecn/portainer-cn
再添加docker服务之前,被监听的docker需要开启2375端口
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["harbor-ip:8080","nexus-ip:8082","registry-ip:5000"]
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]
}
Portainer有多种监听方式,这里只演示docker API的方式
根据实际情况填写IP
首页就可以看到对应的docker环境
CIG
既然存在轻量级,那必然就有重量级。接下来就来看下CIG,CIG实际上是由三个组件组成:CAdvisor、InfluxDB、Grafana
-
CAdvisor
CAdvisor 是由 Google 开源的一个用于监控容器资源使用情况的工具。它能够实时收集docker容器的 CPU 使用率、内存使用量、磁盘 I/O、网络流量 等指标,并将这些数据暴露为 Prometheus格式的监控端点 -
InfluxDB
InfluxDB 是一个开源的时序数据库,专门设计用于处理时间序列数据。在 CIG 监控系统中,InfluxDB 用于存储由 CAdvisor收集的容器监控数据 -
Grafana
Grafana 是一个开源的数据可视化工具,用于创建、查看和共享监控仪表盘。在 CIG 监控系统中,Grafana与 InfluxDB 集成,可以直接从 InfluxDB 中查询容器监控数据,并通过可视化方式展示在用户界面上
编写cig.yml如下:
version: '2'
services:
influxdb:
image: tutum/influxdb:0.9
container_name: influxdb
environment:
- PRE_CREATE_DB=cadvisor
ports:
- "8083:8083"
- "8086:8086"
cadvisor:
image: google/cadvisor
container_name: cadvisor
links:
- influxdb:influxsrv
command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxdb:8086
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
grafana:
image: grafana/grafana
container_name: grafana
links:
- influxdb:influxsrv
ports:
- "3000:3000"
environment:
- HTTP_USER=admin
- HTTP_PASS=admin
- INFLUXDB_HOST=influxsrv
- INFLUXDB_PORT=8086
- INFLUXDB_NAME=cadvisor
- INFLUXDB_USER=root
- INFLUXDB_PASS=root
#启动cig
docker-compose -f cig.yml up -d
访问http://192.168.233.135:8083/,查看是否创建cadvisor数据库。如下:
访问http://192.168.233.135:8080/containers/,查看CAdvisor自带的页面,如下:
访问http://192.168.233.135:3000/,用户名密码默认为admin,配置下Grafana折线图,如下:
九、root权限被回收
环境已经差不多了,root权限也要被收回了,之后只能使用普通用户进行操作。这就涉及到docker权限问题,由于之前没有指定用户,从容器内部挂载出来的目录都是root权限,普通用户是没有权限进行操作的,而且容器权限太高,有可能使主机被攻击。这又成了一个不得不解决的问题。先演示一下问题,还是用之前的YAML为例:
#删除原来nginx的日志路径
rm -rf /usr/local/docker/nginx/logs/
#赋予用户权限
chmod 777 /usr/bin/docker-compose
chmod 777 /var/run/docker.sock
#切换用户
su hqd
#启动
docker-compose -f docker-demo.yml up -d
可以看到,就算是用hqd用户启动,容器内映射到宿主机的路径也是root用户,这样hqd用户没权限进行任何操作
幸运的是,docker官方也意识到了这点,并提供了解决方案——用户命名空间。用户命名空间简单来说:就是用户映射,容器内部依旧是root用户操作,而容器内的root,实际上是宿主机的普通用户。像是地方长官在地方是老大,但是到了京城,最大的还得是皇帝
接着看下如何指定用户命名空间:
- 映射从属用户和组 ID
先查看下需要指定用户的id和组id,这里我指定的用户是hqd:
id hqd
这里用户id和组id都是1000,先记下,后边要用到
配置 /etc/subuid 和 /etc/subgid :
#1000就是上边查到的用户id
cat >> /etc/subuid <<-'EOF'
hqd:1000:1
hqd:100000:65536
EOF
#1000就是上边查到的组id
cat >> /etc/subgid <<-'EOF'
hqd:1000:1
hqd:100000:65536
EOF
这里配置需要注意,由于这里的subuid 和 subgid 是空的,所以可以直接写入,如果原本已经存在值需要往下递推,下边是个例子:
a:100000:65536
#165536 = 100000 + 65536
b:165536:65536
#231072 = 165536 + 65536
c:231072:65536
#296608 = 231072 + 65536
hqd:1000:1
hqd:296608:65536
如果是CentOS7,则需要下边配置:
#CentOS需要下边配置
echo "user.max_user_namespaces=15000" >> /etc/sysctl.conf
grubby --args="namespace.unpriv_enable=1 user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
reboot
否则会出现下边错误:
- 启用 userns-remap
修改 /etc/docker/daemon.json 文件,如下:
{
"registry-mirrors": ["加速地址"],
//这里配置是让docker信任该地址,否则会提示https安全问题
"insecure-registries":["harbor-ip:8080","nexus-ip:8082","registry-ip:5000"]
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"],
//这里配置用户名:组名
"userns-remap": "hqd:hqd"
}
开始测试:
systemctl daemon-reload
systemctl restart docker
#删除原有nginx日志
rm -rf /usr/local/docker/nginx/logs
#赋予docker相关命令权限
chmod 777 /usr/bin/docker*
#赋予docker.sock权限
chmod 777 /var/run/docker.sock
#切换成hqd用户
su hqd
#启动
docker-compose -f docker-demo.yml up -d
可以看到,logs文件夹已经变成了hqd用户所有了
用户命名空间已知限制,以下标准Docker功能与在启用了用户命名空间的情况下运行Docker守护程序不兼容:
- 与主机(–pid=host或–network=host)共享PID或NET名称空间
- 外部(卷或存储)驱动程序,这些驱动程序不知道或无法使用守护程序用户映射
- –privileged在docker run未指定的情况下 使用mode标志–userns=host
用户名称空间是一项高级功能,需要与其他功能配合。例如,如果从主机装载了卷,则必须预先安排文件所有权,需要对卷内容的读取或写入访问权限
尽管用用户命名空间的容器进程中的root用户具有该容器内超级用户的许多预期特权,但Linux内核基于内部知识(这是一个用用户命名空间的过程)施加了限制。一个值得注意的限制是无法使用该mknod命令。由root用户运行时,在容器内创建设备的权限被拒绝
总结
docker整体来说,提高了部署效率,特别是多环境下,可以屏蔽环境之间的差异,可以快速部署。但是docker本身也存在权限问题,编排问题,在用的过程中被整的很难受。当然,上边都是个人在使用docker时候的体验和理解,如果存在问题,欢迎大家指出