本章我们将学习如何在maven中集成docker插件,通过maven直接打包docker镜像并将之推送到docker仓库中。
我们这里使用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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tt.k8s</groupId>
<artifactId>docker-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<!-- 生成jar包的名称 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意,这里生成的jar包去除掉版本号。
HelloController.java
@RestController
public class HelloController {
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "Hello " + name + "!";
}
}
本地启动,浏览器输入 http://localhost:8080/hello/Andy
后成功打印出 HelloAndy!
说明应用没问题。
在工程的主目录创建Dockerfile文件,并输入以下内容:
#pull base image
FROM openjdk:8-jdk-alpine
#maintainer
MAINTAINER andy
#expose port 8080
EXPOSE 8080
#default command
CMD java -jar /data/docker-test.jar
#copy docker-test.jar to docker image
ADD ./target/docker-test.jar /data/docker-test.jar
FROM,表示基础镜像,这里我们选择一个openjdk的基础镜像。
MAINTAINER,表示作者,随便写。
EXPOSE,暴露的端口。
ADD,表示将target目录下的jar包拷贝到镜像中的对应目录,前一个目录是执行docker build所在的机器,后一个目录是镜像中的目录。
CMD,表示容器启动执行的命令,可以在启动镜像的时候被覆盖。
ENTRYPOINT,表示容器启动的进入点,不可以被覆盖。
CMD和ENTRYPOINT比较容易混淆,这里略作区分。
比如,按照上面Dockerfile的配置:
(1)如果执行 docker run test
,则会执行CMD后的命令 java-jar/data/docker-test.jar
;
(2)如果执行 docker run test/bin/bash
,则不会执行CMD后的命令,相当于被覆盖了;
如果把上面配置中的CMD换成ENTRYPOINT:
ENTRYPOINT java -jar /data/docker-test.jar
(1)如果执行 docker run test
,则会执行ENTRYPOINT后的命令 java-jar/data/docker-test.jar
;
(2)如果执行 docker run test/bin/bash
,则会执行 java-jar/data/docker-test.jar/bin/bash
,这时候 /bin/bash
相当于变成ENTRYPOINT后面命令的参数了;
总之,ENTRYPOINT后面的命令是一定会执行的,但CMD却不一定。
Maven本身是包含时间插件的,但是那个插件取出来的时间为UTC时间,比中国的时间慢8个小时,看着很不爽。
所以,这里我们集成一个时间插件,用于后面自动生成镜像的版本号。
pom.xml中加入以下plugin:
<!--生成当前时间,maven自带的是UTC时间,不符合要求-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<timestampFormat>yyyyMMddHHmmss</timestampFormat>
</configuration>
<executions>
<execution>
<goals>
<goal>create-timestamp</goal>
</goals>
</execution>
</executions>
<inherited>false</inherited>
</plugin>
现在网上流传的docker插件有两个版本,分别是 docker-maven-plugin
和 dockerfile-maven-plugin
。
它们都是同一个作者写的,作者已经明确声明,前者已经被废弃,所以我们这里使用dockerfile插件。
pom.xml中加入以下plugin:
<!-- dockerfile插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<executions>
<execution>
<id>default</id>
<goals>
<!--如果package时不想用docker打包,就注释掉这个goal-->
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Dockerfile -->
<dockerfile>Dockerfile</dockerfile>
<username>admin</username>
<password>Harbor12345</password>
<!-- 注意这里的前缀填写前面我们harbor的地址 -->
<repository>core.harbor.domain/test/${project.artifactId}</repository>
<tag>${timestamp}</tag>
</configuration>
</plugin>
注意,这里填写我们前面安装的harbor的用户名、密码和仓库地址。
上面的准备工作做完了,在IDEA的右侧找到maven,点击输入命令的那个框,输入以下命令,点击执行,即可构建/推送一步完成。
clean package dockerfile:build dockerfile:push
如果不出意外,你是怎么都不会成功的。
如果报连接不上localhost:2375,尝试把IDEA重启下,因为docker安装的过程中会修改环境变量DOCKER_HOST,IDEA好像没有感知到,重启下即可。
如果是使用DeskTop方式安装的docker客户端,需要打个勾,自己百度下这个错误就能查到答案了。
这是因为我们使用的是toolbox安装的docker,而实际上docker的操作是在virtual box虚拟机中,所以这里也要修改虚拟机中的hosts,添加core.harbor.domain域名的映射。
打开桌面上的图标 OracleVMVirtualBox
,双击其中正在运行的虚拟机default或者点击上方的“显示”按钮,进入虚拟机内部。
注,这里操作窗口比较小且不可滚动,可以使用XSHELL等工具进入,ip固定为192.168.99.100,用户名为docker,密码为tcuser。
进入后找到/etc/hosts文件添加一行:
# 任意worker节点的ip
xx.xx.xx.xx core.harbor.domain
然后,你会发现没有修改权限,使用 sudo su
切换到root,再次修改保存。
再次执行maven构建/推送命令,又来个新错误。
这是因为docker客户端没有配置docker仓库的证书。
打开harbor,选择项目test,点击“镜像仓库”标签页,点击“注册证书”。
在虚拟机的 /etc/docker/certs.d/core.harbor.domain/
目录下新建 ca.crt
文件,并把刚才下载的证书的内容拷贝进去,保存退出。
root@default:/home/docker# mkdir -p /etc/docker/certs.d/core.harbor.domain/
root@default:/home/docker# cd /etc/docker/certs.d/core.harbor.domain/
root@default:/etc/docker/certs.d/core.harbor.domain# vi ca.crt
root@default:/etc/docker/certs.d/core.harbor.domain# ls
ca.crt
注,这里无法直接使用上传命令,使用拷贝粘贴的方式更快捷。
再次执行maven构建/推送命令,又来个新错误。
这是因为docker客户端没有登录到我们的私仓。
虽然,上面我们maven插件中配置了用户名/密码,但是似乎不生效。
打开docker客户端(命令行),登录到远程仓库:
$ docker login core.harbor.domain
Authenticating with existing credentials...
Stored credentials invalid or expired
Username (alan): admin
Password:
WARNING! Your password will be stored unencrypted in C:\Users\01384359\.docker\config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
再次执行maven构建/推送命令,又来个新错误。
报了个413错误,请求体太大,查看镜像的大小大概有122M的样子。
出现这个错误,一般是nginx上传文件时做了限制,这时候我们想一下,我们的harbor是通过Ingress访问的,Ingress是需要一个Ingress Controller的,还记得我们当时装的nginx ingress吗?对了,这里使用的就是nginx。
这时候我们通过kuboard,打开任意一个nginx ingress的web版ssh。
点击终端进入docker容器内部,找到harbor的配置文件,你肯定会问我怎么知道harbor的配置文件在这里,我只能说这只能靠经验了。
root@nginx-ingress-55dfk:/# cd /etc/nginx/conf.d/
root@nginx-ingress-55dfk:/etc/nginx/conf.d# ls
harbor-harbor-harbor-ingress.conf kube-system-kuboard.conf
root@nginx-ingress-55dfk:/etc/nginx/conf.d# cat harbor-harbor-harbor-ingress.conf
# 省略...
location / {
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
client_max_body_size 1m;
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-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering on;
proxy_pass http://harbor-harbor-harbor-ingress-core.harbor.domain-harbor-harbor-portal-80;
}
# 省略...
可以看到,上面的 client_max_body_size
的值为1m,这也就意味着上传文件的大小不能超过1M。
我们肯定不能直接修改这里的配置,因为nginx ingress一重启就又会变回1M。
这时候反过来查看harbor ingress的配置。
[root@master kubelet]# kubectl get ingress -n harbor
NAME HOSTS ADDRESS PORTS AGE
harbor-harbor-ingress core.harbor.domain,notary.harbor.domain 80, 443 4h1m
[root@master kubelet]# kubectl edit ingress harbor-harbor-ingress -n harbor
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/proxy-body-size: "0"
ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
creationTimestamp: "2019-11-01T07:32:14Z"
generation: 1
# 省略...
发现这里有一坨参数,也就是 metadata.annotations
下面的部分,添加一行参数:
nginx.org/client-max-body-size: 500m
保存退出,这里的配置会自动同步到nginx ingress那边。
注,这里有个坑,出现这个错误,你去官网或者百度或者谷歌,几乎所有的资料都显示让你修改 proxy-body-size
,但是实际上修改这个参数并没有一点效果。
注,还有另外一种方式是修改nginx ingress的ConfigMap中添加一行参数 client-max-body-size:500m
。
[root@master kubelet]# kubectl get configmap -n nginx-ingress
NAME DATA AGE
nginx-config 1 2d2h
[root@master kubelet]# kubectl edit configmap nginx-config -n nginx-ingress
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
server-names-hash-bucket-size: "1024"
# client-max-body-size: 500m
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"server-names-hash-bucket-size":"1024"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"nginx-config","namespace":"nginx-ingress"}}
creationTimestamp: "2019-10-30T08:48:50Z"
name: nginx-config
namespace: nginx-ingress
resourceVersion: "2986"
selfLink: /api/v1/namespaces/nginx-ingress/configmaps/nginx-config
uid: 38f98f6a-2107-4a42-9b82-58003606d371
注,如果遇到超时的问题,同样是修改Ingress的参数。
再次执行maven构建/推送命令,哎哟我操,终于成功了。
打开项目test,发现下面多了个镜像,可以点进去看看,有一个标签(版本号),正是我们刚才提交的时间。
本章的内容比较多,主要是坑有点多。
(1)maven中配置dockerfile插件;
(2)本地虚拟机中需要配置hosts;
(3)本地虚拟机中需要存放harbor仓库的证书;
(4)Ingress需要修改nginx上传文件body大小的限制。
坑其多也,踩之爽也。