基于Ubuntu20.04在k8s 1.25部署gin+MySQL服务

0. 前言

某天突发奇想,既然都学了 docker 了,那干脆,顺便把 kubernetes 也学了,于是开始了我长达一个月的环境搭建、踩坑历程。

最开始,我的想法是,在我的物理机使用 WSL + docker 来部署服务,但是 WSL 部署的服务好像只是单机版,和实际生产中的情况相差甚远,于是,我去弄了几台服务器,一台阿里云 2C2G,一台腾讯云 4C8G,一台腾讯云 2C2G。

基于本人比较喜欢折腾的特点,我没有选择常见的 CentOS 来搭建,而是使用了 Ubuntu (问就是平时用 WSL 用多了,对 Ubuntu 有了感情 bushi)。然后就开始了我漫长的异地组网历程。记得前后搭建了半个多月吧,前面七天基本在搭建环境,解决镜像源问题,后面七天在解决两个 node 之间的通信,后面发现,我租用的服务器,都是弹性服务器,没法换公网和内网的 ip,目前跨 VPC 构建 k8s 集群不是一个好方法 (毕竟企业不可能这样做,最多也就是学生搞来玩玩),没办法,只好自己搞虚拟机了,不过还好,又历经一周,虚拟机的搭建成功了,后面如果能搞到更多磁盘和内存的话,可能会尝试双 master 和多 node 的集群。

1. 环境搭建

1.1 环境说明

节点名称、ip:

  • master:192.168.22.222
  • node1:192.168.22.223

master 要求 至少 2G RAM,2 核 CPU

1.2 版本信息
  • 系统版本:Ubuntu server 20.04.6
  • Docker:20.10.21
  • Kubernetes:1.25.0
1.3 环境配置

设置主机名及解析

# master
sudo systemctl set-hosename master
sudo cat > /etc/hosts << EOF
192.168.22.222 master
192.168.22.223 node1
EOF


# node1systemctl set-hosename node1
sudo cat > /etc/hosts << EOF
192.168.22.222 master
192.168.22.223 node1
EOF

关闭 swap

sudo swapoff -a
# 注释/etc/fstab文件的最后一行
sudo sed -i '/swap/s/^/#/' /etc/fstab

开启 IPv4 转发

sudo cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 分割线
modprobe overlay
modprobe br_netfilter
# 分割线
sudo cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
#分割线
sudo sysctl --system
1.4 安装 containerd

从 k8s 1.25 开始使用 containerd 来作为底层容器支持,根据 k8s 和 containerd 的匹配要求,这里我们使用 containerd 1.7.0

# apt 安装无法安装最新的版本,这里使用tar包解压
wget https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz 
tar zxvf containerd-1.7.0-linux-amd64.tar.gz -C /usr/local
# 导出默认配置
sudo containerd config default > /etc/containerd/config.toml
# 编辑配置文件
sudo vim /etc/containerd/config.toml
# 进入到 vim 后搜索、替换
# 修改sandbox_image行替换为aliyun的pause镜像
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.8"

# 配置 systemd cgroup 驱动 
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  ...
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true
    
# 配置镜像加速
[plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins. "io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
          endpoint = ["https://registry.aliyuncs.com"]

添加 containerd 服务

sudo cat > /etc/systemd/system/containerd.service << EOF
# Copyright The containerd Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
#uncomment to enable the experimental sbservice (sandboxed) version of containerd/cri integration
#Environment="ENABLE_CRI_SANDBOXES=sandboxed"
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
EOF

加载配置,启动 contained 服务

systemctl daemon-reload
systemctl enable --now containerd
1.5 安装 kubernetes

安装必要组件

sudo apt-get install -y apt-transport-https ca-certificates curl

添加阿里云安装源

sudo cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

gpg --keyserver keyserver.ubuntu.com --recv-keys BA07F4FB
gpg --export --armor BA07F4FB | sudo apt-key add -
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -

安装 k8s

sudo apt-get update
sudo apt-get install -y kubelet=1.25.0-00 kubeadm=1.25.0-00 kubectl=1.25.0-00
systemctl enable --now kubelet 

标记软件包,避免自动更新

sudo apt-mark hold kubelet kubeadm kubectl
1.6 安装 docker
apt install docker.io
1.7 初始化 kubernetes 集群

使用 kubeadm 初始化

# 这里的apiserver那行是master的ip, 注意service-cidr和pod-network-cidr和节点ip不要出现在同一网段
kubeadm init \
--image-repository registry.aliyuncs.com/google_containers \
--apiserver-advertise-address=192.168.22.222 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=22.22.0.0/16 \
--kubernetes-version v1.25.0

如果没有报错的话,配置环境变量

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
export KUBECONFIG=/etc/kubernetes/admin.conf

node1 端输入类似这样的命令

# 记得在 node1 开放6443端口
kubeadm join 192.168.22.223:6443 --token dc4wxa.qar86v4pb1b2umvm \
        --discovery-token-ca-cert-hash sha256:1df0074a2226ed1a56f53b9d33bf263c51d3794b4c4b9d6132f07b68592ac38a 
# token 是随机生成的

重新生成 token

kubeadm token create --print-join-command
1.8 安装 calico 网络插件

流行的有 flannel 和 calico,这里选择 calico

wget https://raw.staticdn.net/projectcalico/calico/v3.24.1/manifests/tigera-operator.yaml
sudo kubectl create -f tigera-operator.yaml
wget https://raw.staticdn.net/projectcalico/calico/v3.24.1/manifests/custom-resources.yaml
vim custom-resources.yaml
# 修改cidr配置
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    # Note: The ipPools section cannot be modified post-install.
    ipPools:
    - blockSize: 26
      cidr: 10.244.0.0/16 # 修改为刚刚初始化时的 pod-network-cidr
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()      

注意,custom-resources.yamlspec.calicoNetwork.ipPools.cidr 一定要和刚刚初始化的 pod-network-cidr 一致,不然无法添加 calico 插件

查看 pod 状态

$ kubectl get pod -A
NAMESPACE          NAME                                       READY   STATUS    RESTARTS       AGE
calico-apiserver   calico-apiserver-95575566-mpv54            1/1     Running   4 (156m ago)   3d1h
calico-apiserver   calico-apiserver-95575566-n4x5w            1/1     Running   4 (156m ago)   3d1h
calico-system      calico-kube-controllers-85666c5b94-lll7q   1/1     Running   4 (156m ago)   3d1h
calico-system      calico-node-djqts                          1/1     Running   4 (14h ago)    3d1h
calico-system      calico-node-wp4cf                          1/1     Running   4 (156m ago)   3d1h
calico-system      calico-typha-76fd59d84d-xn79m              1/1     Running   6 (156m ago)   3d1h
calico-system      csi-node-driver-74p7m                      2/2     Running   8 (156m ago)   3d1h
calico-system      csi-node-driver-t86b2                      2/2     Running   8 (14h ago)    3d1h
kube-system        coredns-c676cc86f-tmq7f                    1/1     Running   4 (156m ago)   3d1h
kube-system        etcd-master                                1/1     Running   4 (156m ago)   3d1h
kube-system        kube-apiserver-master                      1/1     Running   4 (156m ago)   3d1h
kube-system        kube-controller-manager-master             1/1     Running   4 (156m ago)   3d1h
kube-system        kube-proxy-449rk                           1/1     Running   4 (14h ago)    3d1h
kube-system        kube-proxy-vswhw                           1/1     Running   4 (156m ago)   3d1h
kube-system        kube-scheduler-master                      1/1     Running   4 (156m ago)   3d1h
tigera-operator    tigera-operator-6675dc47f4-wcfxf           1/1     Running   7 (155m ago)   3d1h

2. 部署 MySQL

先编写一个 mysql-deploy.yaml 配置文件

apiVersion: apps/v1                             # apiserver的版本
kind: Deployment                                # 副本控制器deployment,管理pod和RS
metadata:
  name: mysql                                   # deployment的名称,全局唯一
  namespace: default                            # deployment所在的命名空间
  labels:
    app: mysql
spec:
  replicas: 1                                   # Pod副本期待数量
  selector:
    matchLabels:                                # 定义RS的标签
      app: mysql                                # 符合目标的Pod拥有此标签
  strategy:                                     # 定义升级的策略
    type: RollingUpdate                         # 滚动升级,逐步替换的策略
  template:                                     # 根据此模板创建Pod的副本(实例)
    metadata:
      labels:
        app: mysql                              # Pod副本的标签,对应RS的Selector
    spec:
      nodeName: node1                           # 指定pod运行在的node
      containers:                               # Pod里容器的定义部分
        - name: mysql                           # 容器的名称
          image: mysql:8.0                      # 容器对应的docker镜像
          volumeMounts:                         # 容器内挂载点的定义部分
            - name: time-zone                   # 容器内挂载点名称
              mountPath: /etc/localtime         # 容器内挂载点路径,可以是文件或目录
            - name: mysql-data
              mountPath: /var/lib/mysql         # 容器内mysql的数据目录
            - name: mysql-logs
              mountPath: /var/log/mysql         # 容器内mysql的日志目录
          ports:
            - containerPort: 3306               # 容器暴露的端口号
          env:                                  # 写入到容器内的环境容量
            - name: MYSQL_ROOT_PASSWORD         # 定义了一个mysql的root密码的变量
              value: "root"
      volumes:                                  # 本地需要挂载到容器里的数据卷定义部分
        - name: time-zone                       # 数据卷名称,需要与容器内挂载点名称一致
          hostPath:
            path: /etc/localtime                # 挂载到容器里的路径,将localtime文件挂载到容器里,可让容器使用本地的时区
        - name: mysql-data
          hostPath:
            path: /data/mysql/data              # 本地存放mysql数据的目录
        - name: mysql-logs
          hostPath:
            path: /data/mysql/logs              # 本地存入mysql日志的目录

在编写一个对外提供服务的 mysql-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  type: NodePort
  ports:
    - port: 3306
      targetPort: 3306
      nodePort: 30001
  selector:
    app: mysql

创建服务

kubectl create -f mysql-deploy.yaml
kubectl create -f mysql-svc.yaml

查看节点是否正常运行

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS      AGE
mysql-566cddf86-v65mw              1/1     Running   2 (15h ago)   2d1h

访问数据库,密码的话,刚有在 yaml 中说明,为 root,登陆成功后即可输入数据

kubectl exec -it mysql-566cddf86-v65mw -- mysql -u root -p

开放远程连接权限

FLUSH PRIVILEGES;
/* mysql8.0 只能以这种方式来赋权*/
alter user 'root'@'%' identified with mysql_native_password by 'root';
GRANT SELECT, INSERT, UPDATE, DELETE  ON *.* TO 'root'@'%';
flush privileges;

然后 node1 节点要开放 3306 端口和 30001 端口 (刚刚设置的对外开放的端口),可以在宿主机连接集群的数据库

mysql -u root -h 192.168.22.223 -P 30001 -p

3. 部署 gin 服务

BuyHouse: 一个简单的gin+MySQL数据查询系统,课程实训项目 (gitee.com)

这里使用了学校实训项目的一个 demo,这里我只用到了 gin 部分。

制作镜像以前,我们先看看集群里 MySQL 的 ip

看到 mysql 集群 ip 为 22.22.166.184,那么 gin 里数据库配置 (database/mysql.go) 的 ip 也要改成 22.22.166.184

首先是把项目打包成 docker image,在项目根目录 (go.mod 所在目录),编写 Dockerfile

FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . /app
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o app

FROM alpine AS runner
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 9999:9999
ENTRYPOINT ["./app"]

这里使用多级构建 (实际也就两层,太懒了,不想搞太多了,十多兆已经是我可以接受的大小了 doge),如果不这样做,构建出的镜像差不多 1G,不论是推送到仓库还是拉取,都会很影响效率。

然后执行

docker build -t buy-house .

docker 就会拉取、打包镜像,用 docker images 可以查看多了一个 buy-house 的镜像,如果想要推送到个人仓库的话,执行

docker tag buy-house jaydenchang/buy-house

然后在 Docker 客户端 (已登陆了个人账号) 推送即可。

然后编写 go-deploy.yaml,这里我使用自己制作的镜像,并上传到了个人仓库

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-deployment
  labels:
    app: go
spec:
  selector:
    matchLabels:
      app: go
  replicas: 2
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: go
    spec:
      containers:
      - image: jaydenchang/buy-house:latest
        name: go
        imagePullPolicy: Always
        command: ["./app","-v","v1.3"]
        ports:
        - containerPort: 9999
          protocol: TCP

编写 go-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: go-service
  labels:
    app: go
spec:
  selector:
    app: go
  ports:
    - name: go-port
      protocol: TCP
      port: 9999
      targetPort: 9999
      nodePort: 31080
  type: NodePort

生成节点

kubectl create -f go-svc.yaml
kubectl create -f go-deploy.yaml

检查一下

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS      AGE
busybox                            1/1     Running   2 (14m ago)   173m
go-deployment-66878c4885-twmzs     1/1     Running   0             29s
go-deployment-66878c4885-zxjqb     1/1     Running   0             29s
mysql-566cddf86-v65mw              1/1     Running   3 (14m ago)   2d2h

go 的镜像被分配到了 node1 节点,我们输入 node1IP:port,也就是 192.168.22.223:31080

其他接口就不测试了 (一个拿不出眼的小项目就不展示太多了)。至此,整个部署过程结束。这次小试牛刀,搭建一个比较简易的双节点集群。未来的学习,可能会尝试更复杂的集群部署 (先给自己挖个坑吧)。

参考链接

ubuntu 运行 apt-get update 时阿里云 k8s 安装源报错_已解决_博问_博客园 (cnblogs.com)

一个k8s集群——跨云服务器部署_k8s跨云部署_qq_43285879的博客-CSDN博客

kanzihuang/kubespray-extranet: Create a kubernetes cluster on the public network (github.com)

跨VPC或者跨云供应商搭建K8S集群 - Search (bing.com)

公网环境搭建k8s集群 - ttlv - 博客园 (cnblogs.com)

Kubernetes(k8s)安装以及搭建k8s-Dashboard详解 - 掘金 (juejin.cn)

Kubernetes 1.27 快速安装手册 - 知乎 (zhihu.com)

基于Ubuntu-22.04 kubeadm安装K8s-v1.25.0 | Marshall's blog (aledk.com)

k8s 初始化master节点时无calico,coredns一直是pending状态_calico pending_copa~的博客-CSDN博客

K8s部署自己的web项目_k8s 前端_肖仙女hhh的博客-CSDN博客

公网创建 kubernetes 集群的解决方案 · GitHub

在Linux公网、云服务器搭建K8s集群 - 知乎 (zhihu.com)

(43条消息) kubernetes集群部署nginx应用服务_kubernetes部署nginx_鱼大虾的博客-CSDN博客

k8s集群部署mysql完整过程记录 - blayn - 博客园 (cnblogs.com)

部署go项目到k8s集群 - Jeff的技术栈 - 博客园 (cnblogs.com)

如何给go项目打最小docker镜像,足足降低99%_Scoful的博客-CSDN博客

基于Ubuntu20.04在k8s 1.25部署gin+MySQL服务 | Jayden's Blog (jaydenchang.top)