0%

使用docker快速部署微服务项目

使用docker快速部署微服务项目


前言

一个典型的微服务系统架构如图所示:

从图中可以看到,这里面有大量的第三方服务,如:nacos,MySQL,Minio,Redis、Elasticsearch等,要想快速完整的部署实施一套系统,以传统的裸机安装的方式,保守估计也得1-2天左右才能部署完成。但如果使用docker进行部署,可能10分钟都用不了,整个项目就部署完成了,可以节省很多不必要的时间。

Docker是什么

能快速节省部署时间的这个神器,就是docker。看官网的介绍:https://www.docker.com/resources/what-container

docker的logo

docker的logo非常形象的表达了它的定位。

可能看官网的介绍还是有点模糊,我试着用简单的语言来描述一下:使用docker,可以将程序与程序的运行时环境打包为一个服务进行部署。正如它的logo所表示的意思一样,上面的一个个小集装箱就是我们自己的服务,而docker就是承载这个众多服务的基石。

Docker基本概念

注:以下内容来自于 https://yeasy.gitbook.io/docker_practice/,强烈推荐阅读。

镜像

介绍

Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。

分层存储

因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。

前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

仓库

介绍

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个 仓库Repository);每个仓库可以包含多个 标签Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过 ubuntu:16.04,或者 ubuntu:18.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest

仓库名经常以 两段式路径 形式出现,比如 jwilder/nginx-proxy,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。

Docker Registry 公开服务

Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

最常使用的 Registry 公开服务是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的 官方镜像。除此以外,还有 Red Hat 的 Quay.io;Google 的 Google Container RegistryKubernetes 的镜像使用的就是这个服务;代码托管平台 GitHub 推出的 ghcr.io

由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(Registry Mirror),这些镜像服务被称为 加速器。常见的有 阿里云加速器DaoCloud 加速器 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像,比直接从 Docker Hub 下载速度会提高很多。

国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 网易云镜像服务DaoCloud 镜像市场阿里云镜像库 等。

私有 Docker Registry

除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。

开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。

除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,HarborSonatype Nexus

Dockerfile

Dockerfile是一个用来自定义容器镜像的文本文件。其内包含了一条条的 **指令(Instruction)**,每一条指令构建一层镜像,因此每一条指令的内容,就是描述该层应当如何构建。当使用docker部署项目时,就需要编写Dockerfile来将项目构建成镜像。

项目实战

打包镜像

需要先将项目,打包成tgz格式的部署包,再使用maven的docker插件,生成docker镜像。我这里使用maven的assembly插件来进行打包。

assembly插件

maven中的配置如下:

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
69
70
71
72
73
74
75
76
77
<!-- 指定启动类,将依赖打成外部jar包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<!-- 启动类和依赖包 -->
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.alibaba.nacos.JeecgNacosApplication</mainClass>
</manifest>
<!-- classpath路径 -->
<manifestEntries>
<Class-Path>config/</Class-Path>
</manifestEntries>
</archive>
<!-- 排除在jar外 -->
<excludes>
<exclude>dll/**</exclude>
<exclude>config/**</exclude>
<exclude>mybatis/**</exclude>
<exclude>ftl/**</exclude>
<exclude>static/**</exclude>
<exclude>templates/**</exclude>
<exclude>groovy/**</exclude>
<exclude>*.txt</exclude>
<exclude>*.xml</exclude>
<exclude>*.properties</exclude>
<exclude>*.yml</exclude>
<exclude>*.json</exclude>
<exclude>assembly*</exclude>
<exclude>generate*</exclude>
</excludes>
</configuration>
</plugin>

<!-- 使用assembly打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- 指定源码编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>

<!-- 指定资源文件的编码格式 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>

在resources目录下,新建assembly.xml,

文件内容如下:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">

<!--
必须写,否则打包时会有 assembly ID must be present and non-empty 错误
这个名字最终会追加到打包的名字的末尾,如项目的名字为 speed-api-0.0.1-SNAPSHOT,
则最终生成的包名为 speed-api-0.0.1-SNAPSHOT-bin.zip
-->
<id>bin</id>

<!-- 打包后的文件格式,可以是zip,tar,tar.gz,tar.bz2,jar,war,dir -->
<formats>
<format>tgz</format>
</formats>

<!-- 压缩包下是否生成和项目名相同的根目录 -->
<includeBaseDirectory>false</includeBaseDirectory>

<dependencySets>
<dependencySet>
<!-- 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录 -->
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>

<fileSets>

<!-- 把项目相关的说明文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>

<!-- 把项目的配置文件,打包进zip文件的config目录 -->
<fileSet>
<directory>${project.basedir}/src/main/resources</directory>
<outputDirectory>config</outputDirectory>
<includes>
<include>dll/**</include>
<include>config/**</include>
<include>mybatis/**</include>
<include>static/**</include>
<include>templates/**</include>
<include>groovy/**</include>
<include>*.xml</include>
<include>*.properties</include>
<include>*.yml</include>
<include>*.txt</include>
<include>*.json</include>
</includes>
<excludes>
<exclude>assembly*</exclude>
<exclude>generate*</exclude>
<exclude>application-dev.properties</exclude>
</excludes>
</fileSet>

<!-- 把项目的脚本文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.basedir}/src/main/bin</directory>
<outputDirectory></outputDirectory>
</fileSet>

<!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

配置好后,使用maven开始打包

打包成功后,最终会在项目的target目录,生成最终的部署文件。

dockerfile插件

pom.xml中增加maven插件

1
2
3
4
5
6
7
8
9
10
11
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<dockerfile>Dockerfile</dockerfile>
<repository>${project.artifactId}</repository>
<buildDirectory>${project.build.directory}</buildDirectory>
<tag>${project.version}</tag>
</configuration>
</plugin>

增加此插件后,在idea的maven插件工具上,就可以看到docker相关的命令

在项目的根路径下,创建Dockerfile文件

编辑Dockerfile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM openjdk:8u201-alpine

# 解决openjdk无字库,导致调用awt类库画验证码空指针问题
RUN apk add fontconfig && apk add --update ttf-dejavu && fc-cache --force

#参数定义
ARG HOME_DIR=/home/nacos
ARG TARGET_FILE=./target/jeecg-cloud-nacos-2.4.5-bin.tgz

#工作目录
WORKDIR ${HOME_DIR}
#将本地程序解压到工作目录
ADD ${TARGET_FILE} ${HOME_DIR}
#暴露端口对外访问
EXPOSE 8848
#修复时间跟宿主主机差8小时问题
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
#启动命令
ENTRYPOINT ["java", "-jar", "jeecg-cloud-nacos-2.4.5.jar"]

上面我们已经将项目打包成tgz文件了,现在开始打包生成镜像。

如图所示,双击dockerfile:build命令,开始执行生成镜像命令。

打包成功后,打开本地的docker,可以看到刚刚打包成功的镜像文件

到此为止,我们就完成了打包单个项目的镜像了。如果你的项目只有一个springboot,那么现在就可以使用docker run 命令,执行镜像文件了。

容器编排

当我们的项目,子模块非常多时,如果用docker run命令挨个启动,那就太麻烦了,docker官方为我们提供了多个容器的统一管理工具docker compose

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。

其代码目前在 https://github.com/docker/compose 上开源。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

通过前面的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。

docker-compose的用法,可以参考如下教程:https://yeasy.gitbook.io/docker_practice/compose/introduction

我们先将所有的项目,使用上面的dockerfile插件,统一生成docker镜像,然后来开始编写docker-compose.yml文件来统一管理容器。

如图所示,我已将所有的项目全部生成镜像:

新建一个目录,目录名随便起,在目录下新建一个docker-compose.yml文件,并编辑:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
version: "2"
services:
mysql-jeecg:
container_name: mysql-jeecg
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_ROOT_HOST: '%'
TZ: Asia/Shanghai
restart: always
volumes:
# 数据目录挂载到宿主上
- ./mysql/data:/var/lib/mysql
- ./init.d/mysql:/docker-entrypoint-initdb.d
command:
--max_connections=2000
--default-storage-engine=InnoDB
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
--max_allowed_packet=128M
--innodb_large_prefix=on
--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
--log-bin=mysql-bin
--server-id=1
--binlog_format=ROW
--expire_logs_days=7
ports:
- "3307:3306"

rabbitmq-jeecg:
build:
context: ./init.d/rabbitmq
dockerfile: RabbitMQ.Dockerfile
image: rabbitmq:rabbitmq-jeecg
container_name: rabbitmq-jeecg
ports:
- "5672:5672"
- "15672:15672"
restart: always

redis-jeecg:
container_name: redis-jeecg
image: redis:6.2.12
volumes:
- ./redis:/data
ports:
- "6379:6379"
command:
--appendonly yes
--requirepass "123456"
restart: always

minio-jeecg:
container_name: minio-jeecg
image: minio/minio:latest
ports:
- "9010:9000"
- "9011:9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: 12345678
command:
server --console-address ":9001" /data/minio/
volumes:
- ./minio:/data
restart: always

nginx-jeecg:
container_name: nginx-jeecg
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./web:/usr/share/nginx/html
restart: always

jeecg-gateway:
image: jeecg-cloud-gateway:2.4.5
container_name: jeecg-gateway
volumes:
- ./logs/gateway:/home/gateway/logs/gateway
- ./config/gateway:/home/gateway/config
ports:
- "9999:9999"
depends_on:
- redis-jeecg
restart: always

jeecg-system:
image: jeecg-cloud-system-start:2.4.5
container_name: jeecg-system
volumes:
- ./logs/system:/home/system/logs
- ./config/system:/home/system/config
ports:
- "7001:7001"
depends_on:
- rabbitmq-jeecg
- redis-jeecg
- minio-jeecg
- mysql-jeecg
- jeecg-xxljob
- jeecg-nacos
restart: always

jeecg-xxljob:
image: jeecg-cloud-xxljob:2.4.5
container_name: jeecg-xxljob
volumes:
- ./logs/xxljob:/home/xxljob/logs/xxljob
- ./config/xxljob:/home/xxljob/config
ports:
- "9081:9081"
depends_on:
- mysql-jeecg
restart: always

jeecg-nacos:
image: jeecg-cloud-nacos:2.4.5
container_name: jeecg-nacos
volumes:
- ./logs/nacos:/home/nacos/logs/nacos
- ./config/nacos:/home/nacos/config
ports:
- "8848:8848"
depends_on:
- mysql-jeecg
restart: always

jeecg-base:
image: jeecg-cloud-module-base-start:2.4.5
container_name: jeecg-base
volumes:
- ./logs/base:/home/base/logs
- ./config/base:/home/base/config
depends_on:
- jeecg-nacos
ports:
- "7005:7005"
restart: always

jeecg-contract:
image: jeecg-cloud-module-contract-start:2.4.5
container_name: jeecg-contract
volumes:
- ./logs/contract:/home/contract/logs
- ./config/contract:/home/contract/config
depends_on:
- jeecg-nacos
ports:
- "7004:7004"
restart: always

jeecg-pound:
image: jeecg-cloud-module-pound-start:2.4.5
container_name: jeecg-pound
volumes:
- ./logs/pound:/home/pound/logs
- ./config/pound:/home/pound/config
depends_on:
- jeecg-nacos
ports:
- "7010:7010"
restart: always

jeecg-reports:
image: jeecg-cloud-module-reports-start:2.4.5
container_name: jeecg-reports
volumes:
- ./logs/reports:/home/reports/logs
- ./config/reports:/home/reports/config
depends_on:
- jeecg-nacos
ports:
- "7007:7007"
restart: always

jeecg-vehicle:
image: jeecg-cloud-module-vehicle-start:2.4.5
container_name: jeecg-vehicle
volumes:
- ./logs/vehicle:/home/vehicle/logs
- ./config/vehicle:/home/vehicle/config
depends_on:
- jeecg-nacos
ports:
- "7002:7002"
restart: always

jeecg-wms:
image: jeecg-cloud-module-wms-start:2.4.5
container_name: jeecg-wms
volumes:
- ./logs/wms:/home/wms/logs
- ./config/wms:/home/wms/config
depends_on:
- jeecg-nacos
ports:
- "7003:7003"
restart: always

netty-server:
image: netty-server:1.0
container_name: netty-server
volumes:
- ./logs/netty-server:/home/netty-server/logs
- ./config/netty-server:/home/netty-server/config
ports:
- "11001:10001"
- "11005:10005"
- "11006:10006"
restart: always

在此文件中,我将所有的配置及日志,都挂载到了宿主机上,这样当容器发生故障时,数据也不会丢失,再启动一个容器就行了。

最终的目录如下所示:

使用命令行,进入到此目录,并执行

1
docker-compose up -d

这样所有的项目就都启动了。

特别注意:使用docker部署时,程序里连接数据库或者其他服务时,要使用域名的方式,否则无法连接。域名就是上面docker-compose中配置的容器的名称。

导出镜像

可以使用docker的导出命令,来将项目所有的镜像导出成一个tar包,这样当部署新机器时,直接使用导入命令,将镜像导入,然后使用docker-compose直接启动就行了,中间无需任何配置。

可以使用shell脚本,导出本地所有的镜像

1
2
3
4
5
6
7
#!/bin/bash
sum=`docker images | wc -l`
COUNT=`expr $sum - 1`
echo 镜像数量:$COUNT
TAG=`docker image list|grep -v REPOSITORY|awk '{print $1":" $2}'|awk 'ORS=NR%"'$COUNT'"?" ":"\n"{print}'`
echo TAG值:$TAG
docker save $TAG -o power-material.tar

导入镜像

使用docker load 命令,导入镜像

1
2
#!/bin/bash
docker load -i power-material.tar