1.安装k8s
安装K8S的步骤略去,使用k3s安装会更快捷方便,方便测试环境。
如果使用k3s会有个坑,k3s默认使用container而不是docker作为容器,会导致运行时出现一些问题,后面会详细分析。
安装k3s后,需要按照如下修改 /etc/systemd/system/k3s.service
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
server --docker \
#添加 --docker 参数
2.springboot镜像准备
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github.iminto</groupId>
<artifactId>bcdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bcdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
BcdemoApplication.java 启动器代码略。
src\main\java\com\github\iminto\bcdemo\controller\HomeController.java
package com.github.iminto.bcdemo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@RequestMapping("/")
public String home() {
return "Hello Docker World";
}
}
src\main\resources\application.yaml
server:
port: 9010
mvn打包。
Dockerfile文件如下:
FROM openjdk:8-jdk-alpine
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY bcdemo.jar /opt/app.jar
COPY run.sh /opt/run.sh
EXPOSE 9010
ENTRYPOINT ["/bin/sh", "/opt/run.sh"]
run.sh
#!/bin/bash
# do other things here
java -jar /opt/app.jar 2>&1
需要注意,docker必须要有一个前台进程,不然运行后会马上退出,所以不能使用下面的命令
#这么写是错的
java -jar /opt/app.jar 2>&1 &
docker镜像构建
docker build . -t bcdemo:1.0
测试docker镜像是否正确时,需要用ctrl+p+q终止控制台日志打印,不要用ctrl+c。
3.k8s部署
编写yaml文件
apiVersion: v1
kind: Deployment
metadata:
name: k8s-springboot-demo
labels:
app: k8s-springboot-demo
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: k8s-springboot-demo
template:
metadata:
labels:
app: k8s-springboot-demo
spec:
containers:
- name: k8s-springboot-demo
image: bcdemo:1.0
ports:
- containerPort: 9010
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 9010
initialDelaySeconds: 30
timeoutSeconds: 30
imagePullPolicy: IfNotPresent
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
name: k8s-springboot-demo
namespace: default
labels:
app: k8s-springboot-demo
spec:
ports:
- port: 9010
targetPort: 9010
selector:
app: k8s-springboot-demo
type: NodePort
部署
#部署
kubectl apply -f sp.yaml
#查看运行状态
kubectl get po,svc,deploy -o wide
#删除Deployment
kubectl delete -f sp.yaml
#删除节点
kubectl delete pod k8s-springboot-demo-fc778b44-f4p49
#查看节点详细运行状态,可用于排错
kubectl describe pod k8s-springboot-demo-fc778b44-f4p49
最终部署结果如下:
[root@chenwork2 project]# kubectl get po,svc,deploy -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/k8s-springboot-demo-fc778b44-zfjnx 1/1 Running 0 16h 10.42.0.11 chenwork2 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/k8s-springboot-demo NodePort 10.43.35.126 <none> 9010:31622/TCP 16h app=k8s-springboot-demo
service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 51d <none>
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.extensions/k8s-springboot-demo 1/1 1 1 16h k8s-springboot-demo bcdemo:1.0 app=k8s-springboot-demo
验证:
[root@chenwork2 project]# curl -i -X GET chenwork2:31622/
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 18
Date: Fri, 06 Mar 2020 01:55:40 GMT
Hello Docker World
这样的端口,只能在集群内访问,集群外是无法访问的。
4.k3s的问题
如果pod一直是 ContainerCreating 状态,那就是pod没有创建成功,用describe命令看一下,最后几行一般会看到如下报错
Warning FailedCreatePodSandBox 91s (x29 over 21m) kubelet, host123 Failed create pod sandbox: rpc error: code = Unknown desc = failed to get sandbox image "k8s.gcr.io/pause:3.1": failed to pull image "k8s.gcr.io/pause:3.1": failed to pull and unpack image "k8s.gcr.io/pause:3.1": failed to resolve reference "k8s.gcr.io/pause:3.1": failed to do request: Head https://k8s.gcr.io/v2/pause/manifests/3.1: dial tcp 108.177.97.82:443: i/o timeout
原因已经非常清楚了,failed to pull image “k8s.gcr.io/pause:3.1”,镜像拉不到。
解决方法:
docker pull mirrorgooglecontainers/pause:3.1
#其实直接把rancher/pause镜像命名成k8s.gcr.io/pause即可
docker tag mirrorgooglecontainers/pause:3.1 k8s.gcr.io/pause:3.1
重启k3s,你会发现pod还是启动不起来,这就是前面说的原因:k3s默认使用container而不是docker作为容器
你有了pause镜像,但是k3s不认docker镜像,而是认container容器,所以需要把k3s改成docker运行时。
或者如下操作:
ctr --version
#需要预先导出pause镜像
ctr images import pause-amd64-3.1.tar
#看看有没有加载进来
ctr images list
#如果加载了,但是名字不匹配,需要打标签
ctr images tag gcr.io/google_containers/pause-amd64:3.1 k8s.gcr.io/pause:3.1
5.LoadBalancer服务暴露给外部访问
K8S Service 暴露服务类型有三种:ClusterIP、NodePort、LoadBalancer,三种类型分别有不同的应用场景。
-
对内服务发现,可以使用 ClusterIP 方式对内暴露服务,因为存在 Service 重新创建 IP 会更改的情况,所以不建议直接使用分配的 ClusterIP 方式来内部访问,可以使用 K8S DNS 方式解析,DNS 命名规则为:<svc_name>.<namespace_name>.svc.cluster.local,按照该方式可以直接在集群内部访问对应服务。
-
对外服务暴露,可以采用 NodePort、LoadBalancer 方式对外暴露服务,NodePort 方式使用集群固定 IP,但是端口号是指定范围内随机选择的,每次更新 Service 该 Port 就会更改,不太方便,当然也可以指定固定的 NodePort,但是需要自己维护 Port 列表,也不方便。LoadBalancer 方式使用集群固定 IP 和 NodePort,会额外申请申请一个负载均衡器来转发到对应服务,但是需要底层平台支撑。如果使用 Aliyun、GCE 等云平台商,可以使用该种方式,他们底层会提供 LoadBalancer 支持,直接使用非常方便。
以上方式或多或少都会存在一定的局限性,所以建议如果在公有云上运行,可以使用 LoadBalancer、 Ingress 方式对外提供服务,私有云的话,可以使用 Ingress 通过域名解析来对外提供服务。
下面使用LoadBalancer方式。
yaml文件修改
修改k8s-springboot-demo.yaml
apiVersion: v1
kind: Service
metadata:
name: k8s-springboot-demo
namespace: default
labels:
app: k8s-springboot-demo
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: k8s-springboot-demo
type: LoadBalancer
service的type修改为LoadBalancer,然后
kubectl apply -f k8s-springboot-demo.yaml
命令修改
用命令修改的方式更方便。
[root@chenwork2 project]# kubectl delete svc k8s-springboot-demo
service "k8s-springboot-demo" deleted
[root@chenwork2 project]# kubectl get po,svc,deploy
NAME READY STATUS RESTARTS AGE
pod/k8s-springboot-demo-fc778b44-zfjnx 1/1 Running 0 16h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 51d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.extensions/k8s-springboot-demo 1/1 1 1 16h
[root@chenwork2 project]# kubectl expose deploy k8s-springboot-demo --type=LoadBalancer
service/k8s-springboot-demo exposed
[root@chenwork2 project]# kubectl get po,svc,deploy
NAME READY STATUS RESTARTS AGE
pod/k8s-springboot-demo-fc778b44-zfjnx 1/1 Running 0 16h
pod/svclb-k8s-springboot-demo-crfvw 1/1 Running 0 4s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/k8s-springboot-demo LoadBalancer 10.43.112.54 10.180.249.73 9010:32219/TCP 4s
service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 51d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.extensions/k8s-springboot-demo 1/1 1 1 16h
这样就能在集群外,直接用浏览器访问了。
也可以使用 ClusterIP+kubectl proxy 方式,但不推荐。
6.容器部署yaml中传递参数
可以向容器传递参数到脚本执行一些特殊操作,而且这里变成脚本来启动,这样后续构建镜像基本不需要改 Dockerfile 了
#!/bin/bash
# do other things here
java -jar $JAVA_OPTS /opt/project/app.jar $1 2>&1
上边示例中,我们就注入 $JAVA_OPTS 环境变量,来优化 JVM 参数,还可以传递一个变量,这个变量大家应该就猜到了,就是服务启动加载哪个配置文件参数,例如:–spring.profiles.active=prod 那么,在 Deployment 中就可以通过如下方式配置了:
spec:
containers:
- name: project-name
image: registry.docker.com/project/app:v1.0.0
args: ["--spring.profiles.active=prod"]
env:
- name: JAVA_OPTS
value: "-XX:PermSize=512M -XX:MaxPermSize=512M -Xms1024M -Xmx1024M..."
7.参考
Spring Boot 项目转容器化 K8S 部署实用经验分享
K8s 集群使用 ConfigMap 优雅加载 Spring Boot 配置文件
[Spring Boot应用容器化及Kubernetes部署](http://fly-luck.github.io/2018/11/10/Spring Boot App on Kubernetes/)