Minikube本地部署简单的k8s服务
作者 青鸟
学习了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的可视化界面
1 | minikube dashboard |
部署一个无状态的服务
我们使用golang简单的写一个无状态服务demo,如下所示:
1 | package main |
然后编写dockerfile
1 | FROM scratch |
然后我们将这个打包成docker并上传到本地的image仓库里,具体的代码如下:
1 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GIN_MODE=release go build -o hello |
然后编写一个pod或者deployment的配置文件即可,这里推荐使用deployment,pod是k8s中的基本单位,deployment对pod具有非常好的工作负载管理能力
1 | apiVersion: v1 |
我们使用apply命令将其运行,并检查运行情况
这里的imagePullPolicy: Never
标签很重要,这让k8s可以从本地的镜像中拉取到image
1 | kubectl apply -f hello.yaml -n dev #运行pod |
- hello-deployment.yaml接着运行deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22apiVersion: 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
1 | kubectl apply -f hello-depolyment.yaml -n dev |
更多有关于deployment的使用参考Deployments
至此一个简单的无状态服务便运行在了我们的minikube上
部署一个有状态的服务
有状态的服务部署相比于无状态就复杂了很多了
首先我们来改动一下代码,为golang程序增加一个yaml配置文件,以及数据库的连接
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package 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)
}
}database.go
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
40package 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(
)
}config.yaml
1
2
3
4
5
6database:
name: hello
host: hello-mysql
port: 3306
user: root
pass: "123456"main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package 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
1 | kubectl get cm -n dev |
假如后续的时候,我们需要修改这个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的服务,配置文件如下
- mysql-deployment.yaml
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
74apiVersion: 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就是你使用文件导入时候的文件名,这个时候就不会存在覆盖问题了
- hello-pod.yaml我们运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24apiVersion: 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.yaml1
2
3kubectl apply -f hello-pod.yaml -n dev
查看运行情况
kubectl get pod hello-n dev
然后我们也给出使用deployment部署的配置文件
- hello-deployment.yaml
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
34apiVersion: 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 | kubectl apply -f hello-depolyment.yaml -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 | 启动minikube |
参考文章: