Docker容器化及项目环境管理

3/26/2022 DockerDocker-ComposeDockerfileHarbor容器化项目环境管理私有镜像仓库

# 1. 前言

平时在开发项目的过程中,常常因为开发环境、测试环境、演示环境、正式环境等各种环境的存在且不同从而影响开发进度。开发系统时,项目环境管理的重要性就凸显出来了。通过下面几个实例来了解一下项目环境管理的重要性。

  • 开发人员在开发环境开发好了功能接口,并在开发环境中自测无问题,直接提交到测试环境,测试人员也测试无问题,提交到了正式运行环境,但是偏偏就在正式运行环境报了错,最后排查是正式环境的JDK版本和开发、测试环境的JDK版本不一致,导致代码出错。
  • 开发人员在开发环境中登录测试均无问题,但是测试人员在测试环境中无法登录,最后排查是测试环境的Nginx内部转发出了问题,因为开发环境和测试环境均不是同一个部署,所以部署方式不同,就可能会造成不同的结果。
  • 一个开发完成很久的项目,突然需要给新的客户进行演示,且时间要求很紧张,但是之前的开发环境和测试环境均被收回,那么找人再一步一步安装演示环境就很浪费时间。
  • 客户发现正式环境的数据有部分乱码问题,但是开发人员反复从开发环境寻找问题(因客户是隐私内网部署系统),均未找到问题所在,最后客户那边自查发现是服务器系统编码有问题,虽然客户自查出来了,但是这样的效率是很低的。

从上面几个开发过程中我真实遇到的坑不难看出,项目环境管理势在必行。

# 2. 项目环境管理

# 2.1 环境管理目标及实现

环境管理目标:易部署、易维护、易伸缩、易交接、稳定运行

# 2.1.1 环境管理实现

开发环境使用Docker进行部署,各组件之间使用Docker Network进行内部通信,将打包好的镜像放置到镜像仓库中,测试、演示、正式环境直接从镜像开始构建服务。

环境管理实现

# 2.1.2 网络与映射

将Docker与宿主机的网络进行映射。

网络与映射

# 2.1.3 持久化存储

宿主机存储内容:业务相关数据、业务相关配置、环境相关配置

容器存储内容:业务无关数据、业务无关配置、环境无关配置

持久化存储

# 2.2 人员权限与责任

开发、测试、演示、正式环境的人员权限及责任如下表所示:

环境名称 服务器准备 搭建角色 测试角色 运维角色 访问权限
开发环境 运维人员 开发人员 开发人员 开发人员 开发人员
测试环境 运维人员 测试人员 测试人员 测试人员 测试人员
演示环境 运维人员 开发人员 测试人员 开发人员 开发人员、客户
正式环境 运维人员 运维人员 测试人员 运维人员 客户

# 2.3 实现Docker的不停机更新

要借助Nginx实现Docker的不停机更新,可以采用以下步骤:

  • 在Docker中创建新版本的容器,并将其部署到与旧版本容器相同的网络中。
  • 在Nginx配置文件中添加upstream指令,指向新版本容器的网络地址和端口。
  • 使用nginx -s reload命令重新加载Nginx配置文件,使其生效。
  • 通过逐步将流量从旧版本容器转移到新版本容器,实现无缝更新。可以使用Nginx的upstream模块提供的health_check指令来检查新版本容器的健康状态,并自动切换流量。

需要注意的是,在进行更新前应该进行充分的测试和备份,以确保服务的可靠性和安全性。此外,如果涉及到数据库等有状态服务的更新,还需要考虑数据一致性和数据迁移等问题。

# 3. Docker环境搭建及使用

# 3.1 Docker简介

# 3.1.1 Docker是什么

是什么:Docker是一个用于开发,交付和运行应用程序的开放平台。可以将应用程序与基础架构分开,从而可以快速交付软件。

作用:将一整套环境打包封装成镜像,无需重复配置环境,解决环境带来的种种问题。Docker容器间是进程隔离的,谁也不会影响谁。

官方文档:Docker官方文档 (opens new window)

# 3.1.2 Docker的架构

Docker 其实指代的是用于开发,部署,运行应用的一个平台。平常中说的 Docker 准确来说是 Docker Engine。Docker Engine 是一个 C/S 架构的应用。其中主要的组件有:

  • Docker Server:长时间运行在后台的程序,就是熟悉的 daemon 进程.
  • Docker Client:命令行接口的客户端。
  • REST API:用于和 daemon 进程的交互。

Docker的架构

我们通过给 Docker Client 下发各种指令,然后 Client 通过 Docker daemon 提供的 REST API 接口进行交互,来让 daemon 处理编译,运行,部署容器的繁重工作。 大多数情况下, Docker Client 和 Docker Daemon 运行在同一个系统下,但有时也可以使用 Docker Client 来连接远程的 Docker Daemon 进程,也就是远程的 Server 端。

# 3.1.3 Docker Compose是什么

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Compose 使用的三个步骤:

  • 使用 Dockerfile 定义应用程序的环境。
  • 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
  • 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

Docker-Compose组成

# 3.1.4 Docker与Docker Compose的区别

Docker是一个供开发和运维人员开发,测试,部署和运行应用的容器平台。这种用linux container部署应用的方式叫容器化。

Docker Compose是一个用于运行和管理多个容器化应用的工具。

我们可以列出下列几项来进行二者对比:

  • docker是自动化构建镜像,并启动镜像。 docker compose是自动化编排容器。

  • docker是基于Dockerfile得到images,启动的时候是一个单独的container。

  • docker-compose是基于docker-compose.yml,通常启动的时候是一个服务,这个服务通常由多个container共同组成,并且端口,配置等由docker-compose定义好。

  • 两者都需要安装,但是要使用docker-compose,必须已经安装docker。

# 3.1.5 直接安装和Docker安装的区别

下面以MySQL数据库为例,看看直接安装MySQL和使用Docker安装MySQL有什么区别:

  • docker安装快速,效率高;
  • docker隔离性好,可以安装无数个mysql实例,互相不干扰,只要映射主机端口不同即可;
  • 占用资源少,MB级别,而服务器安装GB级别;
  • 启动速度秒级,而服务器安装启动分钟级别;
  • 性能接近原生,而服务器安装较低;
  • 数据备份、迁移,docker更方便强大;
  • 卸载管理更方便和干净,直接删除容器和镜像即可;
  • 稳定性,只要保证docker环境没问题,mysql就没问题。

# 3.2 Docker环境搭建

# 3.2.1 卸载原先安装的Docker

Debian11系统:

$ dpkg -l | grep docker   # 查询相关软件包
$ sudo apt remove --purge xxx  # 把查出来的软件包执行此命令(替换xxx)
1
2

CentOS7系统:

$ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine
1
2
3
4
5
6
7
8
9
10

# 3.2.2 安装Docker环境

Debian11系统:

方式一:通过脚本安装(推荐)

$ apt-get update -y && apt-get install curl -y  # 安装curl
$ curl https://get.docker.com | sh -   # 安装docker
$ sudo systemctl start docker  # 启动docker服务
$ docker version # 查看docker版本(客户端要与服务端一致)
1
2
3
4

方式二:手动安装

$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg2 \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
$ sudo systemctl start docker
$ docker version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

CentOS7系统:

$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
$ sudo systemctl enable docker
$ sudo systemctl start docker
$ docker version
1
2
3
4
5

# 3.2.3 Docker的GPU环境配置

在Docker中使用GPU,必须在创建容器时打开--gpus参数,并保证docker的版本在19.03以上。

关于配置Docker使用GPU,其实只用装官方提供的toolkit即可。未配置的话会有Error response from daemon: could not select device driver ““ with capabilities: [[gpu]]的报错,解决办法如下:

$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
$ sudo systemctl restart docker
1
2
3
4

# 3.2.4 Docker更换镜像源地址

缘由:在Dockerfile创建镜像拉取基础镜像时遇到了Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)报错,原因是连不上官方的源,可修改配置换源解决。

Docker安装后默认没有daemon.json这个配置文件,需要进行手动创建,配置文件的默认路径:/etc/docker/daemon.json,权限为644,内容如下:

{
 "registry-mirrors":["https://almtd3fa.mirror.aliyuncs.com"],
 "runtimes": {
     "nvidia": {
         "path": "/usr/bin/nvidia-container-runtime",
         "runtimeArgs": []
     }
 }
} 
1
2
3
4
5
6
7
8
9

修改后需要重新加载配置,然后重启docker服务。

$ sudo systemctl daemon-reload
$ systemctl restart docker.service
1
2

# 3.2.5 Docker常用命令

[1] 搜索及拉取docker镜像

$ docker search [NAME]              # 搜索docker镜像(搜索结果里OFFICIAL为OK的是官方镜像)
$ docker pull [IMAGE NAME]          # 拉取指定docker镜像(IMAGE NAME是搜索出来的指定镜像名)
1
2

[2] 查看docker容器实例和镜像

$ docker ps -a                      # 查看所有docker容器实例
$ docker ps                         # 查看所有正在运行的docker容器实例
$ docker images                     # 查看所有docker镜像
$ docker images [IMAGE NAME]        # 查看指定docker镜像(IMAGE NAME为镜像名)
1
2
3
4

[3] 开启停止docker容器实例和镜像

$ docker start [CONTAINER ID/NAMES]   # 开启指定docker容器实例
$ docker stop [CONTAINER ID/NAMES]    # 停止指定docker容器实例
$ docker restart [CONTAINER ID/NAMES] # 重启指定docker容器实例
$ docker start `docker ps -a -q`      # 批量启动所有的docker容器实例
$ docker stop `docker ps -a -q`       # 批量停止所有的docker容器实例
$ docker restart `docker ps -a -q`    # 批量重启所有的docker容器实例
1
2
3
4
5
6

[4] 强制删除docker容器实例和镜像

$ docker rm -f [CONTAINER ID/NAMES]   # 强制删除指定docker容器实例(删除前需先停止实例)
$ docker rmi -f [CONTAINER ID/NAMES]  # 强制删除指定docker镜像(删除前需先停止实例)
$ docker rm -f `docker ps -a -q`      # 批量强制删除所有的docker容器实例(删除前需先停止实例)
$ docker rmi -f `docker images -q`    # 批量强制删除所有的docker镜像(删除前需先停止实例)
1
2
3
4

[5] 进入/退出docker容器内部

$ docker exec -it [CONTAINER ID/NAMES] /bin/bash   # 进入指定docker容器内部
$ exit                                             # 从docker容器内部退出
1
2

注:如果遇到OCI runtime exec failed: exec failed问题,则使用如下命令进入

$ docker exec -it [CONTAINER ID/NAMES] /bin/sh
1

[6] 查看docker运行日志

$ docker logs -f [CONTAINER ID/NAMES] --tail 100    # 查看指定条数的docker运行日志
$ docker logs --since 30m [CONTAINER ID/NAMES]      # 查看指定分钟内的docker运行日志   
1
2

[7] docker容器内部的文件上传和下载

$ docker cp /root/test.txt [CONTAINER ID/NAMES]:/root       # 上传文件
$ docker cp [CONTAINER ID/NAMES]:/root/test.txt /root       # 下载文件
1
2

[8] 让容器使用GPU环境

docker run 的时候加上 --gpus all 即可

--gpus all
1

[9] 在docker容器外执行容器内的命令

有时候我们想执行某个容器的某条命令,但又不想进入容器内,可通过如下命令示例实现:

$ docker exec -it [CONTAINER ID/NAMES] /bin/bash -c 'cd /code && python test.py'
1

注:如果遇到the input device is not a TTY问题,去掉t即可,即:

$ docker exec -i [CONTAINER ID/NAMES] /bin/bash -c 'cd /code && python test.py'
1

[10] docker的跨容器调用

需求情景:爬虫项目和定时任务项目分别在两个容器中部署的,想要在定时任务项目里编写脚本调用爬虫项目中的具体执行文件。

我们可以通过挂载docker.sockdocker命令行客户端实现用docker exec来间接调用。只需要在docker run的时候挂载如下路径即可:

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker
1

[11] 给docker镜像打Tag

$ docker tag [IMAGEID] [REPOSITORY]:[TAG]
1

[12] 给docker容器设置开机自启

$ docker update [CONTAINER ID/NAMES] --restart=always
1

[13] 显示docker容器占用的系统资源

$ docker stats               // stats命令默认会每隔1秒钟刷新一次输出的内容直到你按下ctrl + c
$ docker stats --no-stream   // 如果不想持续的监控容器使用资源的情况,可以通过 --no-stream 选项输出当前的状态
$ docker stats --no-stream [CONTAINER ID/NAMES]  // 只输出指定容器的
$ docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"  // 格式化输出结果,可以只输出部分指标项
1
2
3
4

另注:可使用 ctop (opens new window) 工具监控docker容器占用的资源。

// Linux环境的通用安装
$ sudo wget https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop
$ sudo chmod +x /usr/local/bin/ctop
// ctop的基本使用
$ ctop
1
2
3
4
5

ctop工具的资源监控效果如下图所示:

ctop

[14] 容器进程查看

$ docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Name}}' | grep "PID"  // 根据PID查docker名
$ docker top [CONTAINER ID/NAMES]	  // 列出容器中运行的进程
$ ps -ef   // 查看容器内进程(需要先进入容器内部)
1
2
3

[15] 查看容器内系统版本

$ cat /etc/*release     // 查看容器内系统版本(需要先进入容器内部)
1

[16] 无ENTRYPOINT方式启动

如果是直接执行的代码,写Dockerfile时就不需要加ENTRYPOINT了,然后用以下命令进入容器:

$ docker run -it --name [CONTAINER ID/NAMES] [IMAGE ID/NAMES] /bin/bash
1

如果要覆盖原先Dockerfile里的ENTRYPOINT配置,加个--entrypoint /bin/bash即可。

$ docker run -it --entrypoint /bin/bash --name [CONTAINER ID/NAMES] [IMAGE ID/NAMES]
1

[17] 查看指定容器的元数据

$ docker inspect [CONTAINER ID/NAMES]  // 查看指定容器的元数据
$ docker inspect [CONTAINER ID/NAMES] | grep -i Status -A 10  // 查看容器状态及退出原因
$ docker image inspect [IMAGE NAMES]:latest |grep -i version  // 查看指定latest镜像的版本号
1
2
3

[18] 设置开机自启与取消开机自启

$ docker update --restart=always [CONTAINER ID/NAMES]  // 设置开机自启
$ docker update --restart=no [CONTAINER ID/NAMES]      // 取消开机自启
1
2

[19] docker network相关命令

默认docker之间的网络不互通,如果需要其互相连接,则需要配置docker network。

$ docker network create [network_name]    // 创建网络
$ docker network ls                       // 查看已创建的网络列表
$ docker network inspect [network_name]   // 查看具体的网络详情
$ docker network connect [network_name] [CONTAINER ID/NAMES]      // 将容器加入网络,或者 docker run 时加 --network 进行指定
$ docker network disconnect [network_name] [CONTAINER ID/NAMES]   // 将容器移除网络
$ docker network rm [network_name]        // 删除具体的网络
1
2
3
4
5
6

[20] 查看容器与镜像的差异

$ docker diff [CONTAINER ID/NAMES]   // 显示容器与镜像的差异(修改后的文件)
1

# 3.2.6 清理Docker占用的存储空间

[1] docker空间清理

$ docker system df                 # 类似于Linux上的df命令,用于查看Docker的磁盘使用情况
$ docker ps --size                 # 查看docker容器占用的磁盘空间
$ docker system prune              # 可用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及无tag的镜像)
1
2
3

[2] 查看并清空容器日志

在Linux上,Docker容器日志一般存放在/var/lib/docker/containers/container_id/下面, 以json.log结尾。

手动处理容器日志:

$ docker inspect --format='{{.LogPath}}' [CONTAINER ID/NAMES]       # 查看指定容器的日志
$ echo |sudo tee $(docker inspect --format='{{.LogPath}}' [CONTAINER ID/NAMES])  # 清空指定容器的日志
1
2

批量查找容器日志find_docker_log.sh:

#!/bin/sh

echo "======== docker containers logs file size ========"  

logs=$(find /var/lib/docker/containers/ -name *-json.log)  

for log in $logs  
        do  
             ls -lh $log   
        done  
1
2
3
4
5
6
7
8
9
10

批量清空容器日志 clear_docker_log.sh:

#!/bin/sh 

echo "======== start clean docker containers logs ========"  

logs=$(find /var/lib/docker/containers/ -name *-json.log)  

for log in $logs  
        do  
                echo "clean logs : $log"  
                cat /dev/null > $log  
        done  

echo "======== end clean docker containers logs ========"  
1
2
3
4
5
6
7
8
9
10
11
12
13

注:以上清理日志的方法治标不治本,可通过以下方式设置Docker容器日志大小治本。

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

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

--log-driver json-file  #日志驱动
--log-opt max-size=[0-9+][k|m|g] #文件的大小
--log-opt max-file=[0-9+] #文件数量
1
2
3

方案二:全局设置

编辑文件/etc/docker/daemon.json, 增加以下日志的配置:

"log-driver":"json-file",
"log-opts": {"max-size":"500m", "max-file":"3"}
1
2

解释说明:

  • max-size=500m,意味着一个容器日志大小上限是500M,
  • max-file=3,意味着一个容器有三个日志,分别是id+.json、id+1.json、id+2.json。

然后重启docker守护进程

$ systemctl daemon-reload
$ systemctl restart docker
1
2

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

# 3.2.7 解决Docker容器时区不正确的问题

[1] 修改已运行容器的时区

Step1:进入需要更改时区的容器

$ docker exec -it <容器> /bin/bash
1

Step2:将宿主机的时区链接到容器里

$ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
1

Step3:退出并重启容器

$ exit
$ docker restart <容器>
1
2

[2] 在docker run命令中修改时区

运行容器时,加上挂载参数

$ docker run -d <容器> -v /etc/timezone:/etc/timezone -v /etc/localtime:/etc/localtime
1

或者通过-e TZ="Asia/Shanghai"设置时区:

$ docker run -d <容器> -e TZ="Asia/Shanghai"
1

[3] 在Dockerfile中修改时区

在Dockerfile中

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone
1
2

[4] 在Compose中修改时区

在docker-compose.yml文件中

volumes:
  - /etc/timezone:/etc/timezone
  - /etc/localtime:/etc/localtime
1
2
3

# 3.2.8 查看Latest的镜像具体版本

// 查看容器使用的镜像具体版本
docker inspect minio|grep -i version
// 查看镜像具体版本
docker image inspect minio/minio:latest|grep -i version
1
2
3
4

# 3.2.9 解决 Docker 普通用户无权限问题

给普通用户(如git)添加进Docker组

$ su git                           // 切换git用户
$ sudo usermod -aG docker $USER    // 将当前用户添加到docker组,需要输入git用户密码(忘记了可以在root用户下重置)
$ newgrp docker                    // 激活组权限
1
2
3

# 3.3 Docker Compose环境搭建与基本使用

# 3.3.1 Docker Compose环境搭建

// 下载安装docker-compose,最新版见:https://github.com/docker/compose/releases
$ sudo curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose       
// 赋予docker-compose执行权限
$ sudo chmod +x /usr/local/bin/docker-compose
// 查看docker-compose版本号,验证是否安装成功
$ docker-compose --version
1
2
3
4
5
6

docker-compose

# 3.3.2 Docker Compose基本使用

首先要编写好docker-compose.yml文件,然后构建镜像、运行容器即可。

$ cd /docker-compose-path  // 切换到docker-compose.yml文件所在的目录
$ docker-compose build     // 构建镜像
$ docker-compose up -d     // 运行容器
1
2
3

# 3.4 通过Dockerfile自动构建镜像

Step1:在项目里面再新建一个Dockerfile文件(有的开源项目会提供现成的 Dockerfile,如果没有就要自己去写)

Dockerfile语法

Step2:切换到项目目录里,执行如下命令即可成功构建镜像。

$ docker build -t 'test-image' .
1

Step3:我们可以打包导出镜像,示例如下。

$ docker save test-image > test-image.v1.dockerimage  
1

# 3.4.1 使用Docker部署Springboot项目

Step1:使用Maven将项目打包成jar包,并编写Dockerfile,示例如下:

# 基于java8镜像创建新镜像
FROM java:8
# 作者
MAINTAINER eula
# 将jar包添加到容器中并更名为app.jar
COPY test-project-0.0.1-SNAPSHOT.jar /app.jar
# 安装vim命令
RUN apt-get update && apt-get install vim -y 
# 后台运行jar包
ENTRYPOINT ["nohup","java","-jar","/app.jar","&"]
1
2
3
4
5
6
7
8
9
10

另注:如果想要指定用哪个配置文件,可以使用如下自启动配置

ENTRYPOINT java -jar /app.jar --spring.profiles.active=prod
1

Step2:将jar包和Dockerfile上传到服务器并制作镜像运行容器

$ cd /root/deploy                                                                // 切换到存放jar包和Dockerfile的目录
$ docker build -t test-springboot-image .                                        // 使用Dockerfile构建镜像
$ docker run -d -p 8080:8080 --name test-springboot -e TZ="Asia/Shanghai" test-springboot-image:latest // 通过镜像运行容器
$ docker update test-springboot --restart=always                                 // 设置开机自启
1
2
3
4

# 3.4.2 使用Docker部署Flask项目

Step1:导出项目依赖,并编写Dockerfile,示例如下:

$ pip freeze > requirements.txt
1

注:建议对项目单独建一个conda虚拟环境,再导出依赖,这样导出的依赖就这一个项目的,就不用手动删除无用的了。

# 基于python3.7镜像创建新镜像
FROM python:3.7
# 创建容器内部目录
RUN mkdir /code
# 将项目复制到内部目录
ADD test-project /code/
# 切换到工作目录
WORKDIR /code
# 安装项目依赖
RUN pip install -r requirements.txt
# 安装vim命令
RUN apt-get update && apt-get install vim -y  
# 启动项目
ENTRYPOINT ["nohup","python","server.py","&"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Step2:将项目和Dockerfile上传到服务器并制作镜像运行容器

$ cd /root/deploy                                                       // 切换到存放项目和Dockerfile的目录
$ docker build -t test-flask-image .                                    // 使用Dockerfile构建镜像
$ docker run -d -p 5000:5000 --name test-flask -e TZ="Asia/Shanghai" test-flask-image:latest  // 通过镜像运行容器
$ docker update test-flask --restart=always                             // 设置开机自启
1
2
3
4

# 3.4.3 使用Docker部署前端项目

Step1:将前端项目打包,生成dist文件(或者其他的),编写Dockerfile,示例如下:

# 设置基础镜像
FROM nginx
# 将dist文件中的内容复制到 /usr/share/nginx/html/这个目录下面
COPY dist/  /usr/share/nginx/html/
# 安装vim命令
RUN apt-get update && apt-get install vim -y 
1
2
3
4
5
6

Step2:将项目和Dockerfile上传到服务器并制作镜像运行容器

$ cd /root/deploy                                                     // 切换到存放项目和Dockerfile的目录
$ docker build -t test-web-image .                                    // 使用Dockerfile构建镜像
$ docker run -d -p 8081:80 --name test-web -e TZ="Asia/Shanghai" test-web-image:latest      // 通过镜像运行容器
$ docker update test-web --restart=always                             // 设置开机自启
1
2
3
4

访问地址:http://ip:8081

注:容器内nginx的默认端口是80,如要使用其他端口,请修改nginx配置。以下是容器内的几个重要目录,如有需要可挂载出来。

/etc/nginx/conf.d                                                     // Nginx配置目录
/usr/share/nginx/html                                                 // Nginx存放资源的目录
/var/log/nginx                                                        // Nginx日志目录
1
2
3

另注:如果访问页面时出现403问题,进入容器内修改权限即可。

$ docker exec -it test-web /bin/bash
$ chmod -R 755 /usr/share/nginx/html
1
2

# 3.5 正式环境的前后端分离项目部署

正式环境使用docker network对Docker容器进行统一管理,像数据库这种提供服务的,可不对外提供端口,各容器之间通过hostname进行内部通信。

下面以一个Springboot + Vue的前后端分离项目(项目依赖于MySQL、Redis、 Elasticsearch、Emqx)为例。

Step1:创建docker network及项目依赖环境

$ docker network create yoyo

$ docker run -itd --name yoyo_mysql -h yoyo_mysql --network yoyo -p 3306:3306 \
-e TZ=Asia/Shanghai \
-v /root/docker/mysql/conf:/etc/mysql/conf.d \
-v /root/docker/mysql/logs:/var/log/mysql \
-v /root/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=[password] \
mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
$ docker update yoyo_mysql --restart=always

$ docker run -itd --name yoyo_redis -h yoyo_redis --network yoyo -p 6379:6379 redis:3.2.8 --requirepass "mypassword"
$ docker update yoyo_redis --restart=always

$ docker run -itd --name yoyo_es -h yoyo_es --network yoyo -p 9200:9200 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
elasticsearch:7.16.2
$ docker update yoyo_es --restart=always

$ docker run -itd --name yoyo_emqx -h yoyo_emqx --network yoyo -p 1883:1883 -p 18083:18083 emqx/emqx
$ docker update yoyo_emqx --restart=always
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

注:可使用 docker network ls 命令查看已创建的网络,创建容器时需要使用--network 指定网络,建议用 -h 指定 hostname,除 emqx 的1883端口外,其他服务可不使用 -p 对外映射端口号,我这里为了调试方便,仍然把不必要的端口暴露出来了。

Step2:项目打包并修改配置文件

将Springboot项目打成jar包,Vue项目打成dist包。除此之外,需要修改Springboot项目的配置文件(把项目依赖的MySQL、Redis、 Elasticsearch、Emqx环境地址由原来的ip:port改成 docker 的 hostname),这里采用包外配置。

前端项目打包(以 Angular 为例)

$ npm install -g @angular/cli   
$ npm install   
$ ng build --base-href ./  
1
2
3

后端项目打包(以Springboot为例)

$ mvn clean
$ mvn install
$ mvn package
1
2
3

这部分内容有不熟悉的可以参考我的 Springboot和一些主流框架的整合样例 (opens new window)前端开发环境配置及Vue开发样例 (opens new window) 两篇博客。

Step3:准备项目部署所需要的配置文件及脚本

项目部署所需要文件的目录结构如下:

.
├── config
    ├── application-prod.properties
    └── application.properties
├── dist.zip
├── Dockerfile
├── nginx.conf
├── proxy.conf
├── yoyo_web.conf
├── web_manage-0.0.1.jar
├── unzip.sh
├── build.sh
├── rebuild.sh
└── start_web.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14

[1] 准备 nginx 配置文件 (nginx.conf、yoyo_web.conf、proxy.conf)

nginx.conf(无需修改)

user  root;
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

yoyo_web.conf(需要修改后端的接口地址和前端文件的存放路径,location ~* ^这里根据实际项目的路由配置进行转发)

upstream dev_yoyo_web {
        server 127.0.0.1:8081 weight=1 max_fails=1 fail_timeout=10s;
}
server {
    listen       82;
    server_name  127.0.0.1;
    location / {
        gzip on;
        gzip_vary on;
        gzip_min_length 1k;
        gzip_buffers 16 16k;
        gzip_http_version 1.1;
        gzip_comp_level 9;
        gzip_types text/plain application/javascript application/x-javascript text/css text/xml text/javascript application/json;
        root  /storage/web_code;
        index index.html;
        try_files $uri $uri/ /index.html?$query_string;
    }

    location ~* ^(/login|/logout|/api/|/auth/) {
        proxy_pass http://dev_yoyo_web; 
        client_max_body_size    48m;
        include proxy.conf;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

proxy.conf(无需修改)

proxy_connect_timeout 900s;
proxy_send_timeout 900;
proxy_read_timeout 900;
proxy_buffer_size 32k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_redirect off;
proxy_hide_header Vary;
proxy_set_header Accept-Encoding '';
proxy_set_header Referer $http_referer;
proxy_set_header Cookie $http_cookie;
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_set_header X-Forwarded-Proto $scheme;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

[2] 准备Dockerfile(可不修改,也可根据实际需要修改)

# 设置基础镜像
FROM nginx

# 安装常用命令
RUN apt-get update
RUN apt-get install -y wget       # 安装wget
RUN apt-get install vim -y        # 安装vim
RUN apt-get install -y psmisc     # 安装ps

# 设置工作目录
RUN mkdir /storage
WORKDIR /storage

# 安装java8环境
RUN mkdir /usr/local/java
# 方式一:下载jdk并解压到指定目录(适用于网速快的情况,需要提前安装wget)
RUN wget https://mirrors.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
RUN tar zxvf jdk-8u202-linux-x64.tar.gz -C /usr/local/java && rm -f jdk-8u202-linux-x64.tar.gz
# 方式二:将本地jdk复制到内部目录并自动解压(适用于网速慢的情况,提前下载好)
# ADD jdk-8u202-linux-x64.tar.gz /usr/local/java
# RUN rm -f jdk-8u202-linux-x64.tar.gz
RUN ln -s /usr/local/java/jdk1.8.0_202 /usr/local/java/jdk
ENV JAVA_HOME /usr/local/java/jdk
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib
ENV PATH ${JAVA_HOME}/bin:$PATH

# 放置前端代码及nginx配置
ADD dist/ /storage/web_code
COPY nginx.conf /etc/nginx/nginx.conf
COPY yoyo_web.conf /etc/nginx/conf.d/yoyo_web.conf
COPY proxy.conf /etc/nginx

# 放置后端代码及包外配置
COPY web_manage-0.0.1.jar /storage
COPY config /storage

# 放置启动脚本并授予权限
COPY start_web.sh /storage/start_web.sh
RUN chmod u+x /storage/start_web.sh

# 容器服务自启
ENTRYPOINT ["/storage/start_web.sh"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

注意事项:

  • ENTRYPOINT 里的配置会覆盖父镜像的启动命令,因此这里需要启动 Nginx 和 Jar 的两个命令。若用&&连接的话只会执行前面的那一个,因此这里将两个启动命令都写进一个Shell脚本里。

    关于CMD和ENTRYPOINT有一点需要特别注意:如果一个Dockerfile中有多个CMD或ENTRYPOINT,只有最后一个会生效,前面其他的都会被覆盖。
    
    1
  • 前端包我这里采用的是将 zip 通过shell脚本解压后再拷贝进容器的方式,如果采用 tar.gz 格式,ADD命令会自动对其进行解压(其他压缩格式不可以)。

    ADD dist.tar.gz /storage/web_code
    
    1

[3] 准备部署脚本

unzip.sh(无需修改)

#!/bin/bash

#define default variable
base_path=$(cd `dirname $0`; pwd)
app_path="${base_path}/dist"
zip_name="${base_path}/dist.zip"
rm -fr ${app_path}
unzip -d ${app_path} ${zip_name}
echo "unzip success!"
1
2
3
4
5
6
7
8
9

start_web.sh(可不修改,也可根据实际需要修改)

#!/bin/bash

/docker-entrypoint.sh nginx -g 'daemon off;' &
java -jar /storage/web_manage-0.0.1.jar --spring.profiles.active=prod
1
2
3
4

注意:前面的服务一定要在后台运行,即后面加个&,最后一个服务要以前台运行。否则,全部以前台运行的话,只有第一个服务会启动;全部以后台运行的话,当最后一个服务执行完成后,容器就退出了。

build.sh(可不修改,也可根据实际需要修改)

#!/bin/bash

base_path=$(cd `dirname $0`; pwd)
uploads_path="${base_path}/uploads"
mkdir ${uploads_path}
chmod u+x ${base_path}/unzip.sh
${base_path}/unzip.sh
docker build -t 'yoyo_web_image' .
docker run -itd --name yoyo_web -h yoyo_web --network yoyo -v ${uploads_path}:/storage/web_code/uploads -p 8082:82 -p 8081:8081 -e TZ="Asia/Shanghai" yoyo_web_image
1
2
3
4
5
6
7
8
9

rebuild.sh(可不修改,也可根据实际需要修改)

#!/bin/bash

docker rm -f yoyo_web
docker rmi -f yoyo_web_image
base_path=$(cd `dirname $0`; pwd)
uploads_path="${base_path}/uploads"
mkdir ${uploads_path}
chmod u+x ${base_path}/unzip.sh
${base_path}/unzip.sh
docker build -t 'yoyo_web_image' .
docker run -itd --name yoyo_web -h yoyo_web --network yoyo -v ${uploads_path}:/storage/web_code/uploads -p 8082:82 -p 8081:8081 -e TZ="Asia/Shanghai" yoyo_web_image
1
2
3
4
5
6
7
8
9
10
11

如果没有配置好自启动,也可以在Shell脚本里加上在容器外执行容器内命令的方式启动,但这种方式重启容器后就又需要手动开启了,因此不推荐使用。

docker exec -itd `docker ps |grep yoyo_web |awk '{print $1}'` /bin/bash -c 'java -jar -Duser.timezone=GMT+8 /storage/web_manage-0.0.1.jar > /storage/web_manage-0.0.1.log 2>&1'
docker exec -it `docker ps |grep yoyo_web |awk '{print $1}'` /bin/bash -c 'tail -fn 100 /storage/web_manage-0.0.1.log'
1
2

注意:docker exec -it 这里必须不带上d,否则看不到输出结果。

Step4:打包镜像并创建容器启动项目

1)初次部署

切换到工作目录
$ chmod u+x unzip.sh build.sh rebuild.sh
$ ./build.sh
1
2
3

启动成功后,项目就部署好了,Chrome访问 IP:8082地址即可访问前端页面,8081端口是留给后端的。

2)后续更新

切换到工作目录
把 dist.zip 和 web_manage-0.0.1.jar 更换掉,然后执行 rebuild.sh 脚本即可
1
2

# 3.6 将已有容器部署到其他服务器

步骤简述:将容器保存成镜像——将镜像打成tar包,压缩成tar.gz——使用scp命令将文件传输到目标服务器——将tar.gz解压成tar包,载入镜像——docker run 运行镜像创建容器

Step1:将容器保存成镜像

$ docker ps -a
$ docker commit -a "eula" -m "commit uptime-kuma" 1c786853ea40 eula/uptime-kuma:v1.0
$ docker images
1
2
3

说明:-a后面的是提交用户的用户名,-m后面的是提交信息,1c786853ea40是容器id,最后是镜像名及tag,打包出来的镜像如下:

REPOSITORY                                          TAG            IMAGE ID       CREATED              SIZE
eula/uptime-kuma                                    v1.0           b217262a8fe7   About a minute ago   323MB
1
2

Step2:将镜像打包并压缩

$ docker save -o eula-uptime-kuma-v1.0.tar eula/uptime-kuma:v1.0
$ tar -zcvf eula-uptime-kuma-v1.0.tar.gz eula-uptime-kuma-v1.0.tar 
$ rm -f eula-uptime-kuma-v1.0.tar
1
2
3

Step3:将文件传输到目标服务器

$ scp -P port /root/eula-uptime-kuma-v1.0.tar.gz [email protected]:/root/eula-uptime-kuma-v1.0.tar.gz
1

Step4:解压并载入镜像

$ tar -zxvf eula-uptime-kuma-v1.0.tar.gz
$ docker load -i eula-uptime-kuma-v1.0.tar
$ docker images
$ rm -f eula-uptime-kuma-v1.0.tar
1
2
3
4

载入出来的镜像如下:

REPOSITORY                                      TAG             IMAGE ID        CREATED               SIZE
eula/uptime-kuma                                v1.0            b217262a8fe7    About an hour ago     323MB
1
2

Step5:运行镜像创建容器

$ docker run -d --restart=always -p 3001:3001 --name uptime-kuma eula/uptime-kuma:v1.0
$ docker ps
1
2

注意事项:

[1] 通过容器打Docker镜像要比Dockerfile生成的包要大(里面有很多没用的东西),尽量使用后者,但一些需要离线部署并且需要自动下载算法模型的除外。

[2] 直接对设置挂载的容器打包,会导致通过挂载加进去的文件并没有加进去(打出来的镜像不包含挂载进去的文件),可以再创建个不挂载的容器,把文件替换进去,再对这个不挂载的容器打包。

# 4. Docker搭建中间件服务

# 4.1 Docker-MySQL环境搭建

# 4.1.1 拉取镜像创建实例容器并运行

$ docker pull mysql:5.7
$ docker run -p 3306:3306 --name mysql \
-e TZ=Asia/Shanghai \
-v /root/docker/mysql/conf:/etc/mysql/conf.d \
-v /root/docker/mysql/logs:/var/log/mysql \
-v /root/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=[password] \
-d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
$ docker update mysql --restart=always
1
2
3
4
5
6
7
8
9

命令解释说明:

-p 3306:3306:将主机的3306端口映射到docker容器的3306端口。
--name mysql:运行服务名字
-e TZ=Asia/Shanghai:时区是使用了世界标准时间(UTC)。因为在中国使用,所以需要把时区改成东八区的。
-e MYSQL_ROOT_PASSWORD=[password]:初始化 root 用户的密码。
-d mysql:5.7 : 后台程序运行mysql5.7
--character-set-server=utf8mb4 :设置字符集
--collation-server=utf8mb4_unicode_ci:设置校对集
1
2
3
4
5
6
7

说明:如果是挂载已有的其他服务器数据,可能会出现用户权限问题,如果网络是通的,建议使用Navicat的数据传输功能(工具——数据传输——配置源与目标链接——选择需要传输的数据表即可),数据传输速度很快。

# 4.1.2 创建数据库及用户

在本地使用Navicat工具使用root用户连接上该数据库,使用如下四条命令创建数据库及用户。

--创建新的数据库,并设置数据库编码
$ CREATE DATABASE 你的数据库名 DEFAULT CHARSET=utf8 DEFAULT COLLATE utf8_unicode_ci;

--创建新的用户
$ CREATE USER '你的用户名'@'你的服务器IP' IDENTIFIED BY '你的密码';

--把数据库的管理权限给予刚刚创建的MySQL用户
$ GRANT ALL PRIVILEGES ON *.* TO '你的用户名'@'%' IDENTIFIED BY '你的密码' WITH GRANT OPTION;

--刷新权限,使用设置生效
$ FLUSH PRIVILEGES;
1
2
3
4
5
6
7
8
9
10
11

注:如果连接数据库时出现Access denied for user '用户名'@'某IP' (using password: YES)问题,则是第三句授权出了问题,你的本地外网IP被拦截了,那个'%'代表的是访问IP不受限制。

# 4.2 Docker-Nginx环境搭建

# 4.2.1 拉取镜像创建实例容器并运行

$ docker pull nginx
$ docker run -d --name nginx -p 9999:80 nginx:latest
1
2

# 4.2.2 修改Nginx配置文件

[1] 每次都进入到nginx容器内部修改--适用于临时修改情况

Step1:进入到nginx容器内部

docker exec -it [CONTAINER ID/NAMES] /bin/bash
1

命令解释说明:

- exec 命令代表附着到运行着的容器内部
- -it 是 -i 与 -t两个参数合并写法,-i -t 标志着为我们指定的容器创建了TTY并捕捉了STDIN
- [CONTAINER ID/NAMES] 是我们要进入的容器ID(可以省略后面的部分,能唯一区分即可)或名字
- /bin/bash 指定了执行命令的shell
1
2
3
4

进入到nginx容器内部后,我们可以cd /etc/nginx,可以看到相关的nginx配置文件都在/etc/nginx目录下。而nginx容器内的默认首页html文件目录为/usr/share/nginx/html,日志文件位于/var/log/nginx。执行exit命令可以从容器内部退出。

[2] 将nginx容器内部配置文件挂载到主机--适用于频繁修改情况

Step1:创建挂载目录

这里我为了跟mysql的挂载目录保持一致,也使用了自己创建的/root/docker目录(一般放在/mnt目录,这个是Linux专门的挂载目录)

$ cd /root/docker
$ mkdir -p ./nginx/{conf,html,logs}
1
2

Step2:将容器内的nginx.confdefault.conf文件分别拷贝到主机/root/docker/nginx/root/docker/nginx/conf目录下

$ cd /root/docker/nginx
$ docker cp [CONTAINER ID/NAMES]:/etc/nginx/nginx.conf ./ 
$ docker cp [CONTAINER ID/NAMES]:/etc/nginx/conf.d/default.conf ./conf/
1
2
3

命令解释说明:

- [CONTAINER ID/NAMES] 是我们要进入的容器ID(可以省略后面的部分,能唯一区分即可)或名字
- /etc/nginx/nginx.conf 是容器内部nginx.conf的路径
1
2

Step3:重新创建容器实例

先停止、删除原有的容器实例

$ docker stop [CONTAINER ID/NAMES]              # 停止指定docker容器实例
$ docker rm -f [CONTAINER ID/NAMES]             # 强制删除指定docker容器实例(删除前需先停止实例)
1
2

再重新创建新的容器实例

$ docker run -d --name nginx -p 9999:80 -v /root/docker/nginx/nginx.conf:/etc/nginx/nginx.conf -v /root/docker/nginx/logs:/var/log/nginx -v /root/docker/nginx/html:/usr/share/nginx/html -v /root/docker/nginx/conf:/etc/nginx/conf.d --privileged=true [image-id]
1

命令解释说明:

-v 挂载目录,表示将主机目录与容器目录之间进行共享
--privileged=true 容器内部对挂载的目录拥有读写等特权
1
2

Step4:设置开机自启

$ docker update nginx --restart=always
1

# 4.2.3 测试Nginx环境

Step1:新建测试用的index.html文件(不配置会出现403报错)

$ cd /root/docker/nginx/html
$ touch index.html
$ echo "hello world" >> index.html
1
2
3

Step2:打开Chrome浏览器,地址输入IP:port,出现hello world即配置成功。

附:Nginx的常用管理命令

$ nginx -t                  # 检查nginx配置的语法是否正确
$ nginx -s reload           # 重新加载配置文件,而nginx服务不会中断
1
2

# 4.2.4 搭建过程踩的坑

[1] 非安全端口问题

情景描述:搭建完的nginx在本地用curl IP:port可以访问(当然在nginx容器里使用curl 127.0.0.1也是可以访问的),但在Chrome浏览器内找不到该地址(提示“该网页可能已永久移到新的网址”)

错误原因:创建nginx容器时误用了Chrome浏览器的默认非安全端口,访问会直接被拦截,因而出现了该情况。Chrome 默认非安全端口列表如下:

1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667, 6668, 6669
1

解决办法:删掉nginx容器重新搭建,创建nginx容器时避开Chrome浏览器的默认非安全端口即可。

[2] 访问资源403问题

情景描述:部署的项目有上传文件功能,上传成功后要在网页上进行显示,但该资源却403无权限访问,改目录权限777虽然可以临时使其可以访问,但后续上传的文件又权限不足

错误原因:启动nginx的用户没有该资源的访问权限

解决办法:修改nginx的启动用户为root,访问权限就有了。

$ vim /etc/nginx/nginx.conf    // 把第一行的用户配置改成“user  root;”
1

# 4.3 Docker-Oracle环境搭建

# 4.3.1 拉取镜像并运行容器

$ docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g 
$ docker run -d -p 1521:1521 --name oracle11g registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g
$ docker update oracle11g --restart=always
1
2
3

# 4.3.2 进入容器进行配置

Step1:进入容器,切换到root用户

$ docker exec -it oracle11g /bin/bash  # 进入oracle11g容器
$ su root  # 默认密码:helowin (可通过passwd命令修改成自己的)
1
2

Step2:配置环境变量

$ vi /etc/profile
1

在末尾加上:

export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2
export ORACLE_SID=helowin
export PATH=$ORACLEHOME/bin:PATH
1
2
3

Step3:创建软连接,并用oracle用户登录

$ ln -s $ORACLE_HOME/bin/sqlplus /usr/bin   # 创建软链接
$ su - oracle    # 切换到oracle用户
1
2

# 4.3.3 修改密码创建用户

$ sqlplus /nolog  #
$ conn / as sysdba  # 以dba身份登录

# 修改用户system、sys用户的密码 
$ alter user system identified by system;   
$ alter user sys identified by sys;
$ ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;
1
2
3
4
5
6
7

# 4.3.4 用连接工具登录

在PLSQL里使用 system/system 账号连接,注意服务名不是orcl,而是helowin。

具体可查看tnsnames.ora文件的配置:

$ vi /home/oracle/app/oracle/product/11.2.0/dbhome_2/network/admin/tnsnames.ora
1

# 4.4 Docker-MongoDB环境搭建

# 4.4.1 拉取镜像并运行容器

这个mongodb未设置账号密码,仅限内网测试使用。

$ docker pull mongo:latest
$ mkdir -p /root/docker/mongodb/data
$ docker run -itd --name mongodb -v /root/docker/mongodb/data:/data/db -p 27017:27017 mongo:latest
1
2
3

# 4.4.2 Mongodb可视化查看

使用Navicat工具连接查看,账号密码验证空着即可,可使用如下命令查看版本。

$ db.version();
1

# 4.5 Docker-RabbitMQ环境搭建

# 4.5.1 拉取镜像并运行容器

$ docker pull rabbitmq:3.8-management
$ docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 rabbitmq:3.8-management
1
2

注:默认RabbitMQ镜像是不带web端管理插件的,所以指定了镜像tag为3.8-management,表示下载包含web管理插件版本镜像。

# 4.5.2 RabbitMQ创建用户并可视化查看

用Chrome访问http://ip:15672即可访问RabbitMQ的Web端管理界面,默认用户名和密码都是guest,出现如下界面代表已经成功了。

RabbitMQ

默认的 guest 账户有访问限制,只能通过本地网络访问,远程网络访问受限,所以在使用时我们一般另外添加用户。

$ docker exec -i -t rabbitmq  bin/bash  
$ rabbitmqctl add_user root 123456   // 添加用户(实际密码设置复杂一些)
$ rabbitmqctl set_permissions -p / root ".*" ".*" ".*"   // 赋予root用户所有权限
$ rabbitmqctl set_user_tags root administrator           // 赋予root用户administrator角色
$ rabbitmqctl list_users  // 查看所有用户即可看到root用户已经添加成功
$ exit 
1
2
3
4
5
6

# 4.6 Docker-Kafka环境搭建

以下使用 Docker Compose 搭建单机版 Kafka 服务、集群版Kafka,搭建Docker Compose环境见本文5.3节

# 4.6.1 部署单机版Kafka

[1] 部署ZooKeeper及单机版 Kafka 服务

kafka的运行依赖于zookeeper,因而编写zookeeper与kafka的编排文件docker-compose.yml内容如下:

version: '3.2'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - "2181:2181"
    restart: always
  kafka:
    image: wurstmeister/kafka
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://IP:9092
      - KAFKA_LISTENERS=PLAINTEXT://:9092
    volumes:
      - ./docker.sock:/var/run/docker.sock
    restart: always
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注:KAFKA_ADVERTISED_LISTENERS 填写为 PLAINTEXT://IP:9092,这里的 IP 填写成你的公网 IP,如果没带上这个的话,PC是无法连接到服务器上的 kafka 服务的。这里搭建的 kafka 服务仅用于测试,没有设置用户名及密码,勿用于公网生产环境。

编写完毕后,在该文件下的目录下依次执行下面两条命令即可构建好zookeeper和kafka容器:

$ docker-compose build     // 构建镜像
$ docker-compose up -d     // 运行容器
1
2

配置文件目录:/opt/kafka_2.13-2.8.1/config

[2] 验证Kafka是否搭建成功

进入到kafka容器中 并创建topic生产者,执行如下命令:

$ docker exec -it kafka /bin/bash
$ cd /opt/kafka_2.13-2.8.1/bin/
$ ./kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 --partitions 8 --topic test
$ ./kafka-console-producer.sh --broker-list localhost:9092 --topic test
1
2
3
4

执行上述命令后,另起一个窗口,执行如下命令,创建kafka消费者消费消息。

$ docker exec -it kafka /bin/bash
$ cd /opt/kafka_2.13-2.8.1/bin/
$ ./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
1
2
3

执行完上诉命令后,在生产者窗口中输入任意内容回车,即可在消费者的窗口查看到消息。

注:kafka_2.13-2.8.1的含义为,2.13是Scala版本,2.8.1是Kafka版本。

# 4.6.2 部署集群版Kafka

把编排文件docker-compose.yml修改成如下内容,即可部署集群版Kafka(如下是3个节点,如果需要更多可以在后面继续追加)

version: '3.3'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - 2181:2181
    volumes:
      - ./data/zookeeper/data:/data
      - ./data/zookeeper/datalog:/datalog
      - ./data/zookeeper/logs:/logs
    restart: always
  kafka1:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka1
    ports:
      - 9092:9092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://IP:9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 168
    volumes:
      - ./data/kafka1/data:/data/kafka-data
    restart: unless-stopped  
  kafka2:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka2
    ports:
      - 9093:9093
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://IP:9093
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9093
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 168
    volumes:
      - ./data/kafka2/data:/data/kafka-data
    restart: unless-stopped
  kafka3:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    container_name: kafka3
    ports:
      - 9094:9094
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://IP:9094
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9094
      KAFKA_LOG_DIRS: /data/kafka-data
      KAFKA_LOG_RETENTION_HOURS: 168
    volumes:
      - ./data/kafka3/data:/data/kafka-data
    restart: unless-stopped
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 4.6.3 搭建SASL账号密码验证的Kafka

自0.9.0.0版本开始Kafka社区添加了许多功能用于提高Kafka的安全性,Kafka提供SSL或者SASL两种安全策略。SSL方式主要是通过CA令牌实现,此处主要介绍SASL方式。

新建一个目录,放置以下4个文件(需要改动的只有server_jaas.conf)

$ mkdir -p ./kafka-sasl/conf
1

zoo.cfg

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/opt/zookeeper-3.4.13/data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
autopurge.purgeInterval=1

authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
requireClientAuthScheme=sasl
jaasLoginRenew=3600000
zookeeper.sasl.client=true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

server_jaas.conf

Client {
    org.apache.zookeeper.server.auth.DigestLoginModule required
    username="admin"
    password="your_password";
};

Server {
    org.apache.zookeeper.server.auth.DigestLoginModule required
    username="admin"
    password="your_password"
    user_super="your_password"
    user_admin="your_password";
};

KafkaServer {
    org.apache.kafka.common.security.plain.PlainLoginModule required
    username="admin"
    password="your_password"
    user_admin="your_password";
};

KafkaClient {
    org.apache.kafka.common.security.plain.PlainLoginModule required
    username="admin"
    password="your_password";
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

log4j.properties

# Define some default values that can be overridden by system properties
zookeeper.root.logger=INFO, CONSOLE
zookeeper.console.threshold=INFO
zookeeper.log.dir=.
zookeeper.log.file=zookeeper.log
zookeeper.log.threshold=DEBUG
zookeeper.tracelog.dir=.
zookeeper.tracelog.file=zookeeper_trace.log

#
# ZooKeeper Logging Configuration
#

# Format is "<default threshold> (, <appender>)+

# DEFAULT: console appender only
log4j.rootLogger=${zookeeper.root.logger}

# Example with rolling log file
#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE

# Example with rolling log file and tracing
#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE

#
# Log INFO level and above messages to the console
#
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

#
# Add ROLLINGFILE to rootLogger to get log file output
#    Log DEBUG level and above messages to a log file
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}

# Max log file size of 10MB
log4j.appender.ROLLINGFILE.MaxFileSize=10MB
# uncomment the next line to limit number of backup files
log4j.appender.ROLLINGFILE.MaxBackupIndex=10

log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n


#
# Add TRACEFILE to rootLogger to get log file output
#    Log DEBUG level and above messages to a log file
log4j.appender.TRACEFILE=org.apache.log4j.FileAppender
log4j.appender.TRACEFILE.Threshold=TRACE
log4j.appender.TRACEFILE.File=${zookeeper.tracelog.dir}/${zookeeper.tracelog.file}

log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout
### Notice we are including log4j's NDC here (%x)
log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

configuration.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="configuration">
<html>
<body>
<table border="1">
<tr>
 <td>name</td>
 <td>value</td>
 <td>description</td>
</tr>
<xsl:for-each select="property">
<tr>
  <td><a name="{name}"><xsl:value-of select="name"/></a></td>
  <td><xsl:value-of select="value"/></td>
  <td><xsl:value-of select="description"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

然后再创建一个 Docker Compose 编排文件。

docker-compose.yml

version: "3"

services:

  zookeeper:
    image: wurstmeister/zookeeper
    hostname: zookeeper_sasl
    container_name: zookeeper_sasl
    restart: always
    ports:
      - 2181:2181
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      SERVER_JVMFLAGS: -Djava.security.auth.login.config=/opt/zookeeper-3.4.13/secrets/server_jaas.conf
    volumes:
      - ./kafka-sasl/conf:/opt/zookeeper-3.4.13/conf
      - ./kafka-sasl/conf/:/opt/zookeeper-3.4.13/secrets/ 

  kafka:
    image: wurstmeister/kafka:2.11-0.11.0.3
    restart: always
    hostname: broker
    container_name: kafka_sasl
    depends_on:
      - zookeeper
    ports:
      - 9092:9092
    environment:
      KAFKA_BROKER_ID: 0
      KAFKA_ADVERTISED_LISTENERS: SASL_PLAINTEXT://IP:9092
      KAFKA_ADVERTISED_PORT: 9092 
      KAFKA_LISTENERS: SASL_PLAINTEXT://0.0.0.0:9092
      KAFKA_SECURITY_INTER_BROKER_PROTOCOL: SASL_PLAINTEXT
      KAFKA_PORT: 9092 
      KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: PLAIN
      KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
      KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.auth.SimpleAclAuthorizer
      KAFKA_SUPER_USERS: User:admin
      KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" #设置为true,ACL机制为黑名单机制,只有黑名单中的用户无法访问,默认为false,ACL机制为白名单机制,只有白名单中的用户可以访问
      KAFKA_ZOOKEEPER_CONNECT: zookeeper_sasl:2181
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_OPTS: -Djava.security.auth.login.config=/opt/kafka/secrets/server_jaas.conf
    volumes:
      - ./kafka-sasl/conf/:/opt/kafka/secrets/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

编写完毕后,在该文件下的目录下依次执行下面两条命令即可构建好zookeeper和kafka容器:

$ docker-compose build     // 构建镜像
$ docker-compose up -d     // 运行容器
1
2

代码请求测试:

# -*- coding: utf-8 -*-

import time
import json
from datetime import datetime
from kafka import KafkaProducer


def producer_event(server_info):
    producer = KafkaProducer(bootstrap_servers=server_info,
                             security_protocol='SASL_PLAINTEXT',
                             sasl_mechanism='PLAIN',
                             sasl_plain_username='admin',
                             sasl_plain_password='your_password')
    topic = "test_kafka_topic"
    print("kafka连接成功")
    for i in range(7200):
        data = {
            "name": "hello world"
        }
        data_json = json.dumps(data)
        producer.send(topic, data_json.encode()).get(timeout=30)
        print("数据推送成功,当前时间为:{},数据为:{}".format(datetime.now(), data_json))
        time.sleep(1)
    producer.close()


server = "IP:9092"
producer_event(server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 4.6.4 搭建kafka管理平台

[1] kafka-map

kafka-map是一个美观简洁且强大的kafka web管理工具。

项目地址:https://github.com/dushixiang/kafka-map (opens new window)

docker run -d \
    -p 8080:8080 \
    -v /root/kafka-map/data:/usr/local/kafka-map/data \
    -e DEFAULT_USERNAME=your_user \
    -e DEFAULT_PASSWORD=your_password \
    --name kafka-map \
    --restart always dushixiang/kafka-map:latest
1
2
3
4
5
6
7

用Chrome访问http://ip:8080即可访问 kafka-map 管理界面

kafka-map

注:如果配置了4.6.3节的SASL账号密码验证,这里安全验证选择“SASL_PLAINTEXT”,协议机制选择“PLAIN”(虽然连上了,但一些功能不好使了)

[2] kafka-manager

kafka-manager是目前最受欢迎的kafka集群管理工具,最早由雅虎开源,用户可以在Web界面执行一些简单的集群管理操作。

$ docker pull sheepkiller/kafka-manager
$ docker run --name kafka-manager -itd -p 9000:9000 -e ZK_HOSTS="IP:2181" sheepkiller/kafka-manager  // 把IP处换成你的服务器IP地址
1
2

用Chrome访问http://ip:9000即可访问 kafka-manager 管理界面

kafka管理面板-1

连接kafka:点击Cluster,选择Add Cluster,填写Cluster Name(随便起)、Cluster Zookeeper Hosts(zookeeper地址)保存即可。

kafka管理面板-2

[3] KnowStreaming

Know Streaming是一套云原生的Kafka管控平台,脱胎于众多互联网内部多年的Kafka运营实践经验,专注于Kafka运维管控、监控告警、资源治理、多活容灾等核心场景。在用户体验、监控、运维管控上进行了平台化、可视化、智能化的建设,提供一系列特色的功能,极大地方便了用户和运维人员的日常使用。

项目地址:https://github.com/didi/KnowStreaming (opens new window)

官方的一键脚本会将所部署机器上的 MySQL、JDK、ES 等进行删除重装。因此不建议使用它进行部署,下面采用手动部署的方式。

Step0:准备MySQL、ElasticSearch、JDK等基础环境

软件名 版本要求
MySQL v5.7 或 v8.0
ElasticSearch v7.6+
JDK v8+

注:这些环境我之前都用Docker搭建过了,我的版本是MySQL5.7、ElasticSearch7.16.2(KnowStreaming目前不支持使用设置了密码的ES,如果设置了就另外再搭一个吧)、JDK8(官方推荐JDK11,但是JDK8也可以用)

Step1:下载安装包并解压

// 下载安装包
$ wget https://s3-gzpu.didistatic.com/pub/knowstreaming/KnowStreaming-3.0.0-beta.1.tar.gz
// 解压安装包到指定目录
$ tar -zxf KnowStreaming-3.0.0-beta.1.tar.gz -C /data/
1
2
3
4

Step2:导入MySQL数据和ES索引结构

$ cd /data/KnowStreaming

用Navicat创建数据库,create database know_streaming;
打开./init/sql目录,然后执行里面的这5个sql文件,ddl-ks-km.sql、ddl-logi-job.sql、ddl-logi-security.sql、dml-ks-km.sql、dml-logi.sql

打开 ./bin目录,修改一下init_es_template.sh文件里的ES连接信息,执行该脚本。
1
2
3
4
5
6

Step3:修改配置文件

$ cd /data/KnowStreaming
$ vim ./conf/application.yml

修改监听端口、MySQL及ES连接信息
1
2
3
4

Step4:启动项目

在bin目录有官方提供的启动脚本,但我这里因为没用它的那个方式进行搭建JDK,执行该脚本时报错,这里就不用它了。该项目就是个很常规的Java项目,自己启动就行了。

我这里把conf目录的配置文件都剪切到了libs目录,将其与jar包放置在一起,在bin目录写了个start.sh脚本用于启动程序。

#!/bin/bash

#define default variable
app_path="/data/KnowStreaming/libs"
app_log="/data/KnowStreaming/app.log"

if [ -e $app_log ]; then
	touch ${app_log}
fi

#goto directory
cd ${app_path}

#start app
nohup java -jar *.jar  1>${app_log} &
tail -fn 100 ${app_log}
exit 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

启动后,访问http://ip:port地址访问即可,默认账号及密码:admin / admin2022_ 进行登录(另注:v3.0.0-beta.2版本开始,默认账号密码为admin / admin)。若要停止该项目,lsof -i:port搭配 kill -9 PID使用即可。

KnowStreaming

# 4.6.5 不停机查看及修改消息保留时长

需求情景:生产者程序将处理后的数据存入Kafka,但消费者的处理能力不行,数据有大量积压。磁盘还有大量空间,为了防止丢数据,需要在不停机的情况下修改kafka的消息保留时长。

基于时间保留:通过保留期属性,消息就有了TTL(time to live 生存时间)。到期后,消息被标记为删除,从而释放磁盘空间。对于kafka主题中所有消息具有相同的生存时间,但可以在创建主题之前设置属性,或对已存在的主题在运行时修改属性。Kafka支持配置保留策略,可以通过以下三个时间配置属性中的一个来进行调整:log.retention.hourslog.retention.minuteslog.retention.ms,Kafka用更高精度值覆盖低精度值,所以log.retention.ms具有最高的优先级。

以4.6.3节搭建的kafka为例,演示如何查看及不停机修改消息保留时长。

[1] 查看全局的消息保留时长

$ docker exec -it kafka_sasl /bin/bash
$ cd  /opt/kafka_2.11-0.11.0.3
$ grep -i 'log.retention.[hms].*\=' config/server.properties
log.retention.hours=168
1
2
3
4

[2] 不停机修改某个Topic的消息保留时长并查看

$ docker exec -it kafka_sasl /bin/bash
$ cd  /opt/kafka_2.11-0.11.0.3/bin
$ ./kafka-configs.sh --zookeeper zookeeper_sasl:2181 --alter --entity-name yoyo_admin_topic --entity-type topics --add-config retention.ms=60000
Completed Updating config for entity: topic 'yoyo_admin_topic'.
$ ./kafka-topics.sh --describe --zookeeper zookeeper_sasl:2181 --topic yoyo_admin_topic
Topic:yoyo_admin_topic  PartitionCount:1        ReplicationFactor:1     Configs:retention.ms=60000
        Topic: yoyo_admin_topic Partition: 0    Leader: 0       Replicas: 0     Isr: 0
1
2
3
4
5
6
7

注意事项:

  • 需要修改的地方:将zookeeper_sasl:2181换成实际的zookeeper地址,将yoyo_admin_topic换成实际的topic,为了快速看到效果,保留时长仅设置了60000ms,正式修改按照实际的来。
  • 测试流程:提前在topic里写入数据,然后修改topic的消息保留时长并查看,1分钟后去查看该topic的消息是否还存在,发现消息已经被删除了。

# 4.6.6 Kafka分区数应设置多少及默认配置

kafka的每个topic都可以创建多个partition,理论上partition的数量无上限。通常情况下,越多的partition会带来越高的吞吐量,但是同时也会给broker节点带来相应的性能损耗和潜在风险,虽然这些影响很小,但不可忽略,所以确定partition的数量需要权衡一些因素。

[1] 越多的partition可以提供更高的吞吐量

  • 单个partition是kafka并行操作的最小单元。每个partition可以独立接收推送的消息以及被consumer消费,相当于topic的一个子通道,partition和topic的关系就像高速公路的车道和高速公路的关系一样,起始点和终点相同,每个车道都可以独立实现运输,不同的是kafka中不存在车辆变道的说法,入口时选择的车道需要从一而终。
  • kafka的吞吐量显而易见,在资源足够的情况下,partition越多速度越快。这里提到的资源充足解释一下,假设我现在一个partition的最大传输速度为p,目前kafka集群共有三个broker,每个broker的资源足够支撑三个partition最大速度传输,那我的集群最大传输速度为3*3*p=9p。

[2] 越多的分区需要打开更多的文件句柄

  • 在kafka的broker中,每个分区都会对照着文件系统的一个目录。
  • 在kafka的数据日志文件目录中,每个日志数据段都会分配两个文件,一个索引文件和一个数据文件。因此,随着partition的增多,需要的文件句柄数急剧增加,必要时需要调整操作系统允许打开的文件句柄数。

[3] 更多的分区会导致端对端的延迟

  • kafka端对端的延迟为producer端发布消息到consumer端消费消息所需的时间,即consumer接收消息的时间减去produce发布消息的时间。
  • kafka在消息正确接收后才会暴露给消费者,即在保证in-sync副本复制成功之后才会暴露,瓶颈则来自于此。
  • leader broker上的副本从其他broker的leader上复制数据的时候只会开启一个线程,假设partition数量为n,每个副本同步的时间为1ms,那in-sync操作完成所需的时间即n * 1ms,若n为10000,则需要10秒才能返回同步状态,数据才能暴露给消费者,这就导致了较大的端对端的延迟。

[4] 越多的partition意味着需要更多的内存

  • 在新版本的kafka中可以支持批量提交和批量消费,而设置了批量提交和批量消费后,每个partition都会需要一定的内存空间。
  • 无限的partition数量很快就会占据大量的内存,造成性能瓶颈。假设每个partition占用的内存为100k,当partition为100时,producer端和consumer端都需要10M的内存;当partition为100000时,producer端和consumer端则都需要10G内存。

[5] 越多的partition会导致更长时间的恢复期

  • kafka通过多副本复制技术,实现kafka的高可用性和稳定性。每个partition都会有多个副本存在于多个broker中,其中一个副本为leader,其余的为follower。
  • kafka集群其中一个broker出现故障时,在这个broker上的leader会需要在其他broker上重新选择一个副本启动为leader,这个过程由kafka controller来完成,主要是从Zookeeper读取和修改受影响partition的一些元数据信息。
  • 通常情况下,当一个broker有计划的停机,该broker上的partition leader会在broker停机前有次序的一一移走,假设移走一个需要1ms,10个partition leader则需要10ms,这影响很小,并且在移动其中一个leader的时候,其他九个leader是可用的。因此实际上每个partition leader的不可用时间为1ms。但是在宕机情况下,所有的10个partition
  • leader同时无法使用,需要依次移走,最长的leader则需要10ms的不可用时间窗口,平均不可用时间窗口为5.5ms,假设有10000个leader在此宕机的broker上,平均的不可用时间窗口则为5.5s。
  • 更极端的情况是,当时的broker是kafka controller所在的节点,那需要等待新的kafka leader节点在投票中产生并启用,之后新启动的kafka leader还需要从zookeeper中读取每一个partition的元数据信息用于初始化数据。在这之前partition leader的迁移一直处于等待状态。

可以在/config/sever.properties配置文件中,设置默认分区数,以后每次创建topic默认都是分区数。

以4.6.3节搭建的kafka为例,演示如何修改该配置:

$ docker exec -it kafka_sasl /bin/bash
$ cd /opt/kafka_2.13-2.8.1/bin/config
$ vi server.properties
1
2
3

sever.properties里有如下配置,默认分区数为1,我们可以根据自己需要进行修改

# The default number of log partitions per topic. More partitions allow greater
# parallelism for consumption, but this will also result in more files across
# the brokers.
num.partitions=10
1
2
3
4

之后退出容器并重启容器

$ exit
$ docker restart kafka_sasl
1
2

# 4.7 Docker-Redis环境搭建

# 4.7.1 拉取镜像并运行容器

方案一:不使用配置文件启动

$ docker pull redis:3.2.8
$ docker run --name redis -p 6379:6379 -d redis:3.2.8 --requirepass "mypassword" --appendonly yes
$ docker update redis --restart=always
1
2
3

注:--requirepass用来设置密码,--appendonly yes用来设置AOF持久化。

方案二:使用redis.conf配置文件启动

redis容器里没有redis.conf文件,可以从 https://redis.io/docs/management/config/ (opens new window) 地址下载对应版本的配置文件,挂载进去。

$ docker pull redis:3.2.8
$ cd /root/redis
$ wget https://raw.githubusercontent.com/redis/redis/3.2/redis.conf
$ chmod 777 redis.conf
$ vim /root/redis/redis.conf

修改以下配置项
# bind 127.0.0.1 # 这行要注释掉,解除本地连接限制
protected-mode no # 默认yes,如果设置为yes,则只允许在本机的回环连接,其他机器无法连接。
daemonize no # 默认no 为不守护进程模式,docker部署不需要改为yes,docker run -d本身就是后台启动,不然会冲突
requirepass mypassword # 设置密码
appendonly yes # 持久化

$ docker run --name redis \
-p 6379:6379 \
-v /root/redis/redis.conf:/etc/redis/redis.conf \
-v /root/redis/data:/data \
-d redis:3.2.8 redis-server /etc/redis/redis.conf
$ docker update redis --restart=always
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4.7.2 Redis数据库的可视化连接

建议使用 AnotherRedisDesktopManager (opens new window) 开源工具进行可视化连接和管理。

ARDM工具

# 4.8 Docker-ElasticSearch环境搭建

# 4.8.1 拉取镜像并运行容器

部署命令

$ docker pull elasticsearch:7.16.2
$ docker run -d --name es \
-p 9200:9200 -p 9300:9300 \
-v /root/docker/es/data:/usr/share/elasticsearch/data \
-v /root/docker/es/config:/usr/share/elasticsearch/config \
-v /root/docker/es/plugins:/usr/share/elasticsearch/plugins \
-e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms1g -Xmx1g" \
elasticsearch:7.16.2
$ docker update es --restart=always
1
2
3
4
5
6
7
8
9

进入容器进行配置

$ docker exec -it es /bin/bash 
$ cd config
$ chmod o+w elasticsearch.yml
$ vi elasticsearch.yml
1
2
3
4

其中,在 elasticsearch.yml 文件的末尾添加以下三行代码(前两行如果开启则代表允许跨域,出于安全考虑把它关了,第三行开启xpack安全认证)

# http.cors.enabled: true
# http.cors.allow-origin: "*"
xpack.security.enabled: true    
1
2
3

然后把权限修改回来,重启容器,设置账号密码,浏览器访问http://IP:9200地址即可(用 elastic账号 和自己设置的密码登录即可)

$ chmod o-w elasticsearch.yml
$ exit
$ docker restart es
$ docker exec -it es /bin/bash 
$ ./bin/elasticsearch-setup-passwords interactive   // 然后设置一大堆账号密码
1
2
3
4
5

注意事项

1)Elasticsearch请选择7.16.0之后的版本,之前的所有版本都使用了易受攻击的 Log4j2版本,存在严重安全漏洞。

2)ES_JAVA_OPTS="-Xms1g -Xmx1g"只是一个示例,内存设置的少了会导致数据查询速度变慢,具体设置多少要根据业务需求来定,一般而言公司的实际项目要设置8g内存以上。

数据挂载遇到的问题

[1] 数据锁定问题

  • 报错信息:java.lang.IllegalStateException: failed to obtain node locks, tried [[/usr/share/elasticsearch/data]] with lock id [0]; maybe these locations are not writable or multiple nodes were started without increasing

  • 产生原因:ES在运行时会在/data/nodes/具体分片目录里生成一个node.lock文件,由于我是在运行期scp过来的挂载数据,这个也被拷贝过来了,导致数据被锁定。

  • 解决办法:删掉/data/nodes/具体分片/node.lock文件即可

[2] data目录权限问题

  • 解决办法:进入容器内部,把data目录的权限设置为777即可

[3] 集群与单节点问题

  • 解决办法:修改config/elasticsearch.yml里的集群配置即可,如果原来是集群,现在要单节点,就把集群配置去掉。

[4] 堆内存配置问题

  • 报错信息:initial heap size [8589934592] not equal to maximum heap size [17179869184]; this can cause resize pauses

  • 解决办法:-Xms 与 -Xmx 设置成相同大小的内存。

# 4.8.2 可视化管理ES

使用Elasticvue浏览器插件

可借助 Elasticvue (opens new window) Chrome插件实现ES数据库的可视化管理,支持所有版本ES。

elasticvue

使用ElasticHD可视化面板

ElasticHD支持所有版本ES,特色功能是支持“SQL转DSL”。

项目地址:https://github.com/qax-os/ElasticHD (opens new window)

$ docker run -d --name elastichd -p 9800:9800 containerize/elastichd
$ docker update elastichd --restart=always
1
2

浏览器打开http://ip:9800/地址,即可访问面板,在左上角配置ES连接信息即可。如果是带鉴权的ES,按照http://user:[email protected]:9800配置ES连接信息即可。

ElasticHD

在Tools——SQL Convert DSL处,可以编写SQL生成操作ES的DSL语句(作为辅助手段使用,一些复杂的SQL可能不适用)

另注:也可以使用一些在线工具进行转换,例如,https://printlove.cn/tools/sql2es (opens new window)http://sql2dsl.atotoa.com (opens new window)

安装kibana可视化插件

下载与ES版本相同的Kibana

$ mkdir -p /root/kibana
$ cd /root/kibana
$ wget https://artifacts.elastic.co/downloads/kibana/kibana-7.16.2-linux-x86_64.tar.gz
$ tar -zxvf kibana-7.16.2-linux-x86_64.tar.gz
$ cd /root/kibana/kibana-7.16.2-linux-x86_64
$ vi /config/kibana.yml
1
2
3
4
5
6

修改配置文件内容如下(用不到的我这里给删掉了,原配置文件有着很详尽的英文说明):

server.port: 5601
server.host: "ip" 
elasticsearch.hosts: ["http://ip:9200"]
elasticsearch.username: "username"
elasticsearch.password: "password"
i18n.locale: "zh-CN"
1
2
3
4
5
6

启动kibana:

$ cd /root/kibana/kibana-7.16.2-linux-x86_64/bin # 进入可执行目录
$ nohup /root/kibana/kibana-7.16.2-linux-x86_64/bin/kibana & # 启动kibana 
1
2

说明:如果是root用户,会报Kibana should not be run as root. Use --allow-root to continue.的错误,建议切换别的用户去执行,如果就是想用root用户启动,则使用nohup /root/docker/kibana/kibana-7.16.2-linux-x86_64/bin/kibana --allow-root &

启动成功后,浏览器打开http://ip:5601/地址,用es的用户名和密码进行登录,就可以使用了。

Kibana管理面板

关闭kibana:

$ ps -ef | grep kibana
$ kill -9 [PID]
1
2

# 4.8.3 安装ik分词器插件

项目简介:IK 分析插件将 Lucene IK 分析器集成到 elasticsearch 中,支持自定义字典。

项目地址:https://github.com/medcl/elasticsearch-analysis-ik (opens new window)

安装方式:去Releases下载对应ES版本的ik分词器插件,然后上传到Plugins目录将其挂载到容器内。

测试方式:ik分词器有2种算法:ik_smart和ik_max_word,下面我们通过postman工具来测试ik分词器的分词算法。

[1] 测试ik_smart分词

请求url:http://ip:9200/_analyze 请求方式:get

请求参数:

{
    "analyzer":"ik_smart",
    "text":"我爱你,特靠谱"
}
1
2
3
4

[2] 测试ik_max_word分词

请求url:http://ip:9200/_analyze 请求方式:get

请求参数:

{
    "analyzer":"ik_max_word",
    "text":"我爱你,特靠谱"
}
1
2
3
4

上面测试例子可以看到,不管是ik_smart还是ik_max_word算法,都不认为"特靠谱"是一个关键词(ik分词器的自带词库中没有有"特靠谱"这个词),所以将这个词拆成了三个词:特、靠、谱。

自定义词库:ik分词器会把分词库中没有的中文按每个字进行拆分。如果不想被拆分,那么就需要维护一套自己的分词库。

Step1:进入ik分词器路径/config目录,新建一个my.dic文件,添加一些关键词,如"特靠谱"、"靠谱"等,每一行就是一个关键词。

Step2:修改配置文件IKAnalyzer.cfg.xml,配置<entry key="ext_dict"></entry>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">my.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords"></entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
1
2
3
4
5
6
7
8
9
10
11
12
13

Step3:重启ES,并再次使用Postman测试上述请求,发现"特靠谱"、"靠谱"等将其视为一个词了。

# 4.8.4 使用curl命令操作ES

[1] 索引操作

// 查询所有索引
$ curl -u 用户名:密码 http://ip:port/_cat/indices

// 删除索引(包含结构)
$ curl -u 用户名:密码 -XDELETE http://ip:port/索引名

// 清空索引(不包含结构,即删除所有文档)
$ curl -u 用户名:密码 -XPOST 'http://ip:port/索引名/_delete_by_query?refresh&slices=5&pretty' -H 'Content-Type: application/json' -d'{"query": {"match_all": {}}}'

// 创建索引
$ curl -u 用户名:密码 -XPUT 'http://ip:port/索引名' -H 'Content-Type: application/json' -d'
{
    "settings" : {
      "index" : {
        "number_of_shards" : "5",
        "number_of_replicas" : "1"
      }
    },
    "mappings" : {
        "properties" : {
          "post_date": {
               "type": "date"
          },
          "tags": {
               "type": "keyword"
          },
          "title" : {
               "type" : "text"
          }
        }
    }
}'

// 修改索引
$ curl -u 用户名:密码 -XPUT 'http://ip:port/索引名/_mapping' -H 'Content-Type: application/json' -d'
{
  "properties" : {
    "post_date": {
         "type": "date"
    },
    "tags_modify": {
         "type": "keyword"
    },
    "title" : {
         "type" : "text"
    },
    "content": {
         "type": "text"
    }
  }
}'

// 调整副本数量(分片数量不可调整,要修改就只能删除索引重建)
$ curl -u 用户名:密码 -XPUT 'ip:port/索引名/_settings' -H 'Content-Type: application/json' -d '
{
    "index": {
       "number_of_replicas": "0"
    }
}'

// 查看单个索引信息(可以查看到单个索引的数据量)
$ curl -u 用户名:密码 -XGET 'http://ip:port/_cat/indices/index_1?v'

health status index      uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   index_1    aado9-iGRFGN9twQb040ds   5   1   28800345            0        3gb          1.5gb

// 按照文档数量排序索引(可以查看到所有索引的数据量)
$ curl -u 用户名:密码 -XGET 'http://ip:port/_cat/indices?v&s=docs.count:desc'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

注意事项:创建索引时,有的教程在“mappings”里嵌套了“_doc”,会报如下错误,这是因为版本 7.x 不再支持映射类型,将其删除即可。

{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true."}],"type":"illegal_argument_exception","reason":"The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true."},"status":400}%
1

[2] 文档操作

// 根据_id查询文档
$ curl -u 用户名:密码 -XGET 'http://ip:port/索引名/_doc/1'

// 新增和修改文档
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XPOST 'http://ip:port/索引名/_doc/1' -d '
     {
        "msgId": "10010",
        "title": "test-title",
        "content": "test-content",
        "isDeleted": 0,
        "publishTime": "1586707200000",
        "insertTime": "1668212021000",
        "updateTime": "1678687631000"
    }'
         
// 根据_id删除文档
$ curl -u 用户名:密码 -XDELETE "http://ip:port/索引名/_doc/1"

// 查询所有数据
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"match_all":{}}}'

// 查询指定条数的数据
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"match_all":{}},"size":2}'

// 查询指定列数据
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"match_all":{}},"_source":["publishTime","updateTime"]}'

// 查询数据并排序
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"match_all":{}},"sort":{"_id":{"order":"desc"}}}'
 
// 匹配查询
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"match":{"title":"test"}}}'

// 精准查询
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"term":{"title.keyword":"test-title"}}}'

// 范围查询
$ curl -u 用户名:密码 -H "Content-Type:application/json" -XGET http://ip:port/索引名/_search?pretty -d '{"query":{"range":{"msgId":{"gt":"1","lte":"20000"}}}}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 4.9 Docker-EMQX环境搭建

# 4.9.1 拉取镜像并运行容器

$ docker pull emqx/emqx
$ docker run -d --name emqx -p 1883:1883 -p 8086:8086 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
1
2

# 4.9.2 EMQX的管理面板

搭建完后用浏览器访问 http://IP:18083/地址,默认账号及密码为:admin / public,登录后建议立刻修改密码。

EMQX物联网MQTT消息服务器面板

# 4.10 Docker-MinIO环境搭建

# 4.10.1 拉取镜像并运行容器

$ docker pull minio/minio
$ mkdir -p /home/data/minio/data
$ mkdir -p /home/data/minio/config
$ docker run -d --restart always \
   -p 9000:9000 -p 9001:9001 --name minio \
   -e "MINIO_ACCESS_KEY=admin" \
   -e "MINIO_SECRET_KEY=password" \
   -v /home/data/minio/data:/data \
   -v /home/data/minio/config:/root/.minio \
   minio/minio server --console-address ":9001" /data
1
2
3
4
5
6
7
8
9
10

注:密码不可以设置的太简单了(会导致创建失败),出现此问题请查看容器日志。

# 4.10.2 MinIO的管理面板

浏览器打开:http://IP:9001 查看即可。MINIO_ACCESS_KEY为账号,MINIO_SECRET_KEY为密码。进去之后创建存储桶,即可进行使用。

minio-console

# 5. 搭建Harbor私有Docker镜像仓库

# 5.1 镜像仓库及Harbor概述

# 5.1.1 镜像仓库

云原生技术的兴起为企业数字化转型带来新的可能。作为云原生的要素之一,带来更为轻量级虚拟化的容器技术具有举足轻重的推动作用。其实很早之前,容器技术已经有所应用,而 Docker 的出现和兴起彻底带火了容器。其关键因素是 Docker 提供了使用容器的完整工具链,使得容器的上手和使用变得非常简单。工具链中的一个关键,就是定义了新的软件打包格式——容器镜像。镜像包含了软件运行所需要的包含基础 OS 在内的所有依赖,推送至运行时可直接启动。从镜像构建环境到运行环境,镜像的快速分发成为硬需求。同时,大量构建以及依赖的镜像的出现,也给镜像的维护管理带来挑战,镜像仓库的出现成为必然。

镜像仓库

镜像构建之后可以推送至镜像仓库储存和管理,在有应用运行需求时,从仓库拉取特定的应用镜像来运行。镜像仓库作为镜像的分发媒介,可以实现特定的管理和访问控制机制。仓库作为镜像传输流动的主要媒介,成为云原生应用平台运转的核心要件。Docker 开源了其 registry 实现, 目前已经成为 CNCF 的沙箱项目Distribution。不过,Distribution 项目仅仅实现了对镜像存储的支持,对企业级的一些管理诉求并无法提供支持。为了实现企业级镜像仓库的支持,Harbor 项目应运而生。

# 5.1.2 Harbor基本介绍

[1] Harbor发展历史

Harbor Registry 由 VMware 公司中国研发中心云原生实验室原创,并于 2016 年 3 月开源。Harbor 在 Docker Distribution的基础上增加了企业用户必需的权限控制、镜像签名、安全漏洞扫描和远程复制等重要功能,还提供了图形管理界面及面向国内用户的中文支持,开源后迅速在中国开发者和用户社区流行,成为中国云原生用户的主流容器镜像仓库。

2018年7月,VMware 捐赠 Harbor 给 CNCF,使Harbor成为社区共同维护的开源项目,也是首个源自中国的 CNCF 项目。在加入 CNCF 之后,Harbor 融合到全球的云原生社区中,众多的合作伙伴、用户和开发者都参与了Harbor项目的贡献,数以千计的用户在生产系统中部署和使用 Harbor,Harbor 每个月的下载量超过3万次。2020 年 6 月,Harbor 成为首个中国原创的 CNCF 毕业项目。

[2] Harbor是什么

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,虽然Docker官方也提供了公共的镜像仓库,但是从安全和效率等方面考虑,部署企业内部的私有环境Registry是非常必要的,Harbor和docker中央仓库的关系,就类似于nexus和Maven中央仓库的关系,Harbor除了存储和分发镜像外还具有用户管理,项目管理,配置管理和日志查询,高可用部署等主要功能。

项目地址:https://github.com/goharbor/harbor/ (opens new window)

# 5.2 搭建Harbor镜像仓库

# 5.2.1 搭建前的环境准备

搭建Harbor的服务器及基础环境如下:

项目 描述
操作系统 Debian 11 x86_64
Docker 20.10.17
Docker-compose 1.29.2
Harbor 2.7.0

另注:Harbor镜像仓库可以与Drone持续集成配合使用,项目部署后自动保存一份镜像到Harbor,关于Drone的搭建及使用见我的另一篇博客:使用Gitea及Drone搭建轻量持续集成服务 (opens new window)

# 5.2.1 下载安装包并修改配置文件

$ cd /root/Harbor
$ wget https://github.com/goharbor/harbor/releases/download/v2.7.0/harbor-offline-installer-v2.7.0.tgz
$ tar -xvf harbor-offline-installer-v2.7.0.tgz
$ cd harbor
$ cp -ar harbor.yml.tmpl harbor.yml      # 复制配置文件并改名为harbor.yml
$ vim harbor.yml
1
2
3
4
5
6

修改了的配置如下,https的配置整个注释掉,其余的配置项没动。

hostname: 111.111.111.111
http:
  port: 10010
harbor_admin_password: your_harbor_admin_password
database:
  password: your_db_password
data_volume: /data/harbor
1
2
3
4
5
6
7

注:hostname设置成你的服务器IP(这里脱敏成111.111.111.111),http的端口我这里改成了10010,harbor_admin_password是你的harbor管理员登录密码,database我只改了数据库密码,data_volume改了一下挂载路径。配置文件里有详细的注释说明,如果要改其他的,根据说明进行修改即可。

# 5.2.2 安装并启动Harbor

Step1:Harbor安装环境预处理

$ ./prepare
1

Harbor安装环境预处理

Step2:安装并启动Harbor

$ ./install.sh 
1

注:安装Harbor会给构建9个容器,其中容易重名的有nginx、redis,如果之前搭建了的话需要将旧容器重命名一下,否则会出错。

安装并启动Harbor

# 5.2.3 访问Harbor管理面板

访问地址:http://ip:port 用户名:admin 密码:your_harbor_admin_password

Harbor管理面板

# 5.3 使用Harbor镜像仓库

# 5.3.1 修改docker配置并登录

由于docker默认不允许使用非https方式推送和拉取镜像,所以需要修改docker配置。

$ vim /etc/docker/daemon.json
1

修改的内容如下:

{"insecure-registries": ["111.111.111.111:10010"]}
1

然后重载配置并重启docker。

$ systemctl daemon-reload
$ systemctl restart docker
1
2

之后就可以成功docker login了(用户名:admin,密码:your_harbor_admin_password)

$ docker login 111.111.111.111:10010
1

docker-login登录成功

注:如果没有修改docker配置,docker login时会报如下错误

Error response from daemon: Get "https://111.111.111.111:10010/v2/": http: server gave HTTP response to HTTPS client
1

# 5.3.2 上传docker镜像

这里我已经准备好了一个docker镜像(yoyo-web-image:latest)用来测试。

Step1:查看docker镜像并对其打tag

基本格式:docker tag 镜像名:版本 your-ip:端口/项目名称/新的镜像名:版本

$ docker tag yoyo-web-image:latest 111.111.111.111:10010/library/yoyo-web-image:v1.0
1

查看打好tag的docker镜像。

$ docker images
111.111.111.111:10010/library/yoyo-web-image   v1.0            d5b625cc399c   2 weeks ago     951MB
1
2

Step2:推送镜像到harbor仓库

基本格式:docker push 修改的镜像名

$ docker push 111.111.111.111:10010/library/yoyo-web-image:v1.0
1

推送镜像到Harbor仓库

访问Harbor管理面板,点进去library项目,即可查看到刚刚上传的镜像,再点进去可查看详细信息。

在Harbor查看推送成功的镜像

# 5.3.3 拉取docker镜像

这里我换了一台服务器,拉取刚刚上传的docker镜像,在这台服务器上,仍要按照4.1节修改一下docker配置并登录。

在镜像详细信息界面,可以获取到镜像拉取命令。

获取镜像拉取命令

docker login之后,将镜像拉取命令复制到终端即可。

从Harbor仓库拉取镜像

# 6. 参考资料

[1] 项目环境管理思路 from fish-aroma (opens new window)

[2] Debian安装Docker_from 简书 (opens new window)

[3] 直接安装和docker安装的区别 from php中文网 (opens new window)

[4] Docker下安装MySQL from CSDN (opens new window)

[5] 使用docker安装nginx from 掘金 (opens new window)

[6] Docker--删除容器实例和镜像 from 极客分享 (opens new window)

[7] docker与docker-compose介绍,对比与使用 from 简书 (opens new window)

[8] 如何在Debian 9上安装Docker Compose from 腾讯云 (opens new window)

[9] 通过 DockerFile 打包镜像 from cnblog (opens new window)

[10] 如何构建 Docker 镜像 from tkestack (opens new window)

[11] docker安装RabbitMq from 稀土掘金 (opens new window)

[12] docker简易搭建kafka from 知乎 (opens new window)

[13] 中间件docker compose,包含redis、elasticsearch、mongo、mysql、rocketmq、kafka,一键启动 from github (opens new window)

[14] Linux Docker springboot jar 日志时间不正确 from CSDN (opens new window)

[15] docker安装oracle11g(linux环境) from CSDN (opens new window)

[16] Docker 快速安装&搭建 Elasticsearch 环境 from 异常教程 (opens new window)

[17] EMQX docker安装及运行 from CSDN (opens new window)

[18] docker搭建elasticsearch6.8.7并开启x-pack认证 from 程序员宅基地 (opens new window)

[19] Cannot stop or restart a docker container from stackoverflow (opens new window)

[20] Docker启动提示 response from daemon: OCI runtime create failed: container with id exists:XXX:unknown from CSDN (opens new window)

[21] 如何在ubuntu 中彻底删除docker from 腾讯云 (opens new window)

[22] docker可视化工具Portainer部署与汉化 from WebEnh (opens new window)

[23] 关于docker容器内部的文件上传和下载 from 代码先锋网 (opens new window)

[24] docker容器打包成镜像和压缩以及解压和载入镜像 from 程序员宝宝 (opens new window)

[25] 利用Dockerfile部署SpringBoot项目 from 51CTO博客 (opens new window)

[26] 在docker下部署Python项目 from Python Free (opens new window)

[27] docker在容器外执行某个容器内的某个命令 from CSDN (opens new window)

[28] 如何跨容器调用可执行命令 from lyer's blog (opens new window)

[29] 【Docker】daemon.json的作用(八)from CSDN (opens new window)

[30] 清空docker container logs from 暗无天日 (opens new window)

[31] docker 容器日志清理方案 from 简书 (opens new window)

[32] Docker容器使用NFS from cloud-atlas (opens new window)

[33] 解决Docker容器时区不正确的问题 form 简书 (opens new window)

[34] Docker无视防火墙 from fish-aroma (opens new window)

[35] Docker系列-查看Latest的镜像具体版本和查看容器用到的镜像版本 (opens new window)

[36] docker 利用CMD或者ENTRYPOINT命令同时启动多个服务 from CSDN (opens new window)

[37] Docker之docker run参数覆盖Dockerfile中CMD命令以及CMD与ENTRYPOINT的区别 from CSDN (opens new window)

[38] Harbor安装和配置 from Harbor官方文档 (opens new window)

[39] Linux中基于Docker搭建harbor私有镜像仓库 from CSDN (opens new window)

[40] http: server gave HTTP response to HTTPS client from 博客园 (opens new window)

[41] Harbor功能特点看这一篇就够了 from CSDN (opens new window)

Last Updated: 5/16/2023, 10:00:10 AM