作者 青鸟
学习了k8s之后,就一直想上手体验一下。但是直接去部署一个k8s集群所需要的钱是非常昂贵的,于是就参考了k8s官方文档给出来的意见,使用了minikube在本地来部署一个简易的k8s来体验一下
在使用的过程中,遇到了无数的坑,能踩的都踩了个遍,于是乎就有了这篇博客。(强烈建议看官方文档,其他的全是坑)
在本篇博客的本地环境如下:
- macos 14.2.1
- minikube 1.31.2
- kubenetes 1.28.2
首先我们创建一个namespace来隔离服务,将我们这篇博客的所有的服务都运行在隔离环境中。这里我们就创建了一个dev的namespace
1
| kubectl create namespace dev
|
使用下面的命令就可以打开k8s的可视化界面
部署一个无状态的服务#
我们使用golang简单的写一个无状态服务demo,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World!")
})
log.Fatalln(http.ListenAndServe(":80", nil))
}
|
然后编写dockerfile
1
2
3
4
5
| FROM scratch
MAINTAINER cbluebird
ADD hello /app/hello
WORKDIR /app
ENTRYPOINT ["/app/hello"]
|
然后我们将这个打包成docker并上传到本地的image仓库里,具体的代码如下:
1
2
3
4
| CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GIN_MODE=release go build -o hello
docker build -t bluebird/hello .
docker image save bluebird/hello >hello.tar
minikube image load hello.tar
|
然后编写一个pod或者deployment的配置文件即可,这里推荐使用deployment,pod是k8s中的基本单位,deployment对pod具有非常好的工作负载管理能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| apiVersion: v1
kind: Pod
metadata:
name: hello
labels:
name: hello
spec:
containers:
- name: hello
image: bluebird/hello
imagePullPolicy: Never
ports:
- containerPort: 80
hostPort: 8080
|
我们使用apply命令将其运行,并检查运行情况
这里的imagePullPolicy: Never
标签很重要,这让k8s可以从本地的镜像中拉取到image
1
2
3
4
| kubectl apply -f hello.yaml -n dev #运行pod
kubectl describe pods hello -n dev # 查看pod的具体运行情况
minikube ssh --node minikube # 进入命令行工具,在里面curl对应的ip
curl ip:8080 #测试
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deployment
labels:
app: hello
spec:
replicas: 2 # 创建一个 ReplicaSet,其中创建Pod副本的个数
selector: #selector字段定义所创建的ReplicaSet如何查找要管理的Pod,这里通过标签
matchLabels:
app: hello
template: # 定义被创建的pod
metadata: #元数据,这里给pod打上标签
labels:
app: hello
spec: #Pod模板规约,定义容器如何在pod中运行
containers:
- name: hello
image: bluebird/hello
imagePullPolicy: Never
ports:
- containerPort: 80
|
接着运行deployment
1
2
| kubectl apply -f hello-depolyment.yaml -n dev
kubectl get deployments -n dev
|
更多有关于deployment的使用参考Deployments
至此一个简单的无状态服务便运行在了我们的minikube上
部署一个有状态的服务#
有状态的服务部署相比于无状态就复杂了很多了
首先我们来改动一下代码,为golang程序增加一个yaml配置文件,以及数据库的连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| package config
import (
"github.com/spf13/viper"
"log"
)
var Config = viper.New()
func init() {
Config.SetConfigName("config")
Config.SetConfigType("yaml")
Config.AddConfigPath(".")
Config.WatchConfig() // 自动将配置读入Config变量
err := Config.ReadInConfig()
if err != nil {
log.Fatal("Config not find", err)
}
}
|
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
| package database
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"hello/config"
)
var DB *gorm.DB
func Init() { // 初始化数据库
user := config.Config.GetString("database.user")
pass := config.Config.GetString("database.pass")
port := config.Config.GetString("database.port")
host := config.Config.GetString("database.host")
name := config.Config.GetString("database.name")
dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8&parseTime=True&loc=Local", user, pass, host, port, name)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, // 关闭外键约束 提升数据库速度
})
if err != nil {
log.Fatal("DatabaseConnectFailed", err)
}
err = autoMigrate(db)
if err != nil {
log.Fatal("DatabaseMigrateFailed", err)
}
DB = db
}
func autoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
)
}
|
1
2
3
4
5
6
| database:
name: hello
host: hello-mysql
port: 3306
user: root
pass: "123456"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import (
"fmt"
"hello/config/database"
"log"
"net/http"
)
func main() {
database.Init()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World!")
})
log.Fatalln(http.ListenAndServe(":80", nil))
}
|
然后我们和之前无状态的服务一样,打包成一个image上传到本地仓库
ConfigMap#
我们先处理这个config.yaml,我们将这个配置文件处理到configmap中,从而将配置文件由k8s集群来管理,对之后的服务拓展就很方便了。同时我们还可以利用configmap来实现热重载与更新的操作
1
| kubectl create configmap config --from-file=config.yaml -n dev
|
创建一个configmap的方法有四种,我们这里选取一个最为简单与方便的,就是从一个文件中创建一个configmap,然后使用get命令查看创建的configmap
假如后续的时候,我们需要修改这个configmap,我们使用下面命令即可
1
| kubectl edit configmap config -n dev
|
需要查看详细配置的话
1
| kubectl describe cm config -n dev
|
Secret#
我们将初始化的数据库密码写入到Secret中,从而保证其安全性与便捷性
1
| kubectl create secret generic mysql-root-password \ --from-literal=password=123456 -n dev
|
我们就可以在之后的初始化数据库的时候使用它了
部署mysql#
这里我们只给一个单实例有状态应用,即部署一个mysql的pod,假如我们想要部署数据库集群,可以请参考 StatefulSet文档,StatefulSet更适合与集群的部署,这里就简单看一下单实例的mysql,并使用Service来调用这个mysql的服务,配置文件如下
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
| apiVersion: v1
kind: Service
metadata:
name: hello-mysql
labels:
app: hello
spec:
ports:
- port: 3306
selector:
app: hello
tier: mysql
clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: hello
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-mysql
labels:
app: hello
spec:
selector:
matchLabels:
app: hello
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: hello
tier: mysql
spec:
containers:
- image: mysql:8.0
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root-password
key: password
- name: MYSQL_DATABASE
value: hello
- name: MYSQL_USER
value: hello
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root-password
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /root/mysql/data #挂载在服务器上的物理位置
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
|
然后使用命令
1
| kubectl apply -f mysql-deployment.yaml -n dev
|
就可以创建成功了
验证一下
1
| kubectl describe hello-mysql -n dev
|
我们这里使用了动态制卷,在创建 PersistentVolumeClaim 时,将根据 StorageClass 配置动态制备一个 PersistentVolume并挂载在/root/mysql/data,相比于静态,可以更加灵活地释放空间。
注意:不要对应用进行规模扩缩。这里的设置仅适用于单实例应用。下层的 PersistentVolume 仅只能挂载到一个 Pod 上。对于集群级有状态应用,请参考 StatefulSet文档。
这里要注意一个非常坑的点,在minikube中,如果我们想使用数据库可视化工具来访问这个数据库,我们需要部署一个nginx来转发流量。
原因在于,minikube是在部署机器上构建一套虚拟机部署k8s,这里我们有三台主机,第一层是我们本地的主机,第二层是vmware虚拟机,第三层是minikube虚拟机。第一层本机可以直接访问第二层虚拟机,第二层虚拟机可以访问第三层的minikube虚拟机,但是第一层的本机无法直接访问第三层的minikue虚拟机。因此,在构建本地环境时,如果我们是在虚拟机环境里面部署minikube,那么,我们本机是无法直接访问k8s里面的服务的。例如,我们上述部署的k8s MySQL服务,获取的隧道服务连接地址是192.168.49.2:3306,这个地址,我们在虚拟机里面是可以访问的,本机则无法访问。
那么,我们就需要解决本机如何访问k8s服务的问题,我们可以在第二层的虚拟机里面部署一个nginx,然后配置代理端口转发即可。当然k8s也给出了对应的方法我们使用kubectl port-forward
命令也是可以将一个pod的端口映射到一个本地端口上去。
最后我们需要验证mysql服务是否成功部署,我们有两种办法
第一种:直接进入容器的bash,验证即可
第二种:前面 YAML 文件中创建了一个允许集群内其他 Pod 访问的数据库 Service。该 Service 中选项 clusterIP: None
让 Service 的 DNS 名称直接解析为 Pod 的 IP 地址,于是在这个集群内,hello-mysql会被dns直接解析为mysql的地址,这样子更为灵活。 当在一个 Service 下只有一个 Pod 并且不打算增加 Pod 的数量,这是最好的办法。
我们运行 MySQL 客户端以连接到服务器来测试:
1
| kubectl run -it --rm -n dev --image=mysql:8.0 --restart=Never mysql-client -- mysql -h hello-mysql -ppassword
|
这里需要注意这个dev的位置,放在后面的话就会导致在默认的namespace中去查找这个Service,同样的,我们在之后的golang服务中,也是可以直接使用hello-mysql来代替主机地址。
部署应用#
然后编写一个pod或者deployment的配置文件即可,这里推荐使用deployment,pod是k8s中的基本单位,deployment对pod具有非常好的工作负载管理能力
这里有个非常非常坑的点,就是我们使用 volume 将 ConfigMap 作为文件直接挂载configMap的时候,这个时候这个configmap会直接覆盖掉你这个挂载文件夹下的所有东西,可能导致你的可执行文件也被直接覆盖了。
所以最好的方式就是使用subpath只是将configmap中的key作为文件挂载到目录下,而这个key就是你使用文件导入时候的文件名,这个时候就不会存在覆盖问题了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| apiVersion: v1
kind: Pod
metadata:
name: hello
spec:
containers:
- name: hello
image: bluebird/hello
imagePullPolicy: Never
volumeMounts:
- name: config
mountPath: /app/config.yaml ## 镜像里的挂载目录
subPath: config.yaml ## 挂载的文件
readOnly: true
ports:
- containerPort: 80
hostPort: 8080
volumes:
- name: config
configMap:
name: config ## 挂载的configmap的name
items:
- key: config.yaml
path: config.yaml
|
我们运行
1
2
3
| kubectl apply -f hello-pod.yaml -n dev
#查看运行情况
kubectl get pod hello-n dev
|
然后我们也给出使用deployment部署的配置文件
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
| apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deployment
labels:
app: hello
spec:
replicas: 2
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: crk/hello
imagePullPolicy: Never
volumeMounts:
- name: config
mountPath: /app/config.yaml ## 镜像里的挂载目录
subPath: config.yaml ## 挂载的文件
readOnly: true
ports:
- containerPort: 80
volumes:
- name: config
configMap:
name: config ## 挂载的configmap的name
items:
- key: configmap.yaml
path: config.yaml
|
1
2
| kubectl apply -f hello-depolyment.yaml -n dev
kubectl get deployments -n dev
|
k8s的常见命令#
kubectl里的很多命令都是相同的,只需要变换名次就可以对不同的对象操作,比如pod、deployment、confgmap、service等
大致归为:kubectl [command] [TYPE] [NAME] [flags]
- command:指定在一个或多个资源上要执行的操作。例如:create、get、describe、delete、apply等
- TYPE:指定资源类型(如:pod、node、services、deployments等)。资源类型大小写敏感,可以指定单数、复数或缩写形式
- NAME:指定资源的名称。名称大小写敏感。如果省略名称空间,则显示默认名称空间资源的详细信息或者提示:No resources found in default namespace.。
- flags:指定可选的标记。例如,可以使用 -s 或 –server标识来指定Kubernetes API服务器的地址和端口;-n指定名称空间;等等。注意:你从命令行指定的flags将覆盖默认值和任何相应的环境变量。优先级最高。
在多个资源上执行操作时,可以通过类型
TYPE
和名称 NAME
指定每个资源,也可以指定一个或多个文件。
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
| #启动minikube
minikube start
# 运行可视化
minikube dashboard
#从yaml配置文件中运行,不论是pod、deployment等都可以使用,-f指的是文件
kubectl apply -f mysql-deployment.yaml
#通过-n 指定命名空间
kubectl apply -f mysql-deployment.yaml -n dev
#通过yaml里的类型和名称删除
kubectl delete -f hello.yaml
#查看pod详细情况,对于service、deployment,换一下对象就行
kubectl describe pod hello # 查看pod的运行情况
#获取单个pod
kubectl get pod hello
#获取所有pod
kubectl get pods
#编辑configmap
kubectl edit configmap config
#进入容器里的bash
kubectl exec -it podName -n nsName /bin/bash #进入容器
# 查看日志
kubectl logs hello -n dev
|
参考文章: