【Kubernetes】自建Kubernetes集群完全指南(含云主机公网部署方案)

这篇文章也可以在我的博客中查看

说在前面

本文介绍如何使用Kubeadm在自己的服务器上将Kubernetes应用部署为生产环境,而不使用Kuberentes云服务商。

在进行之前,我建议你先保持理智,问自己为什么需要这么做:

  1. 你正在学习Kubernetes,你想知道如何安全地将它部署到生产环境
  2. 你的应用注重硬件性能,你必须使用硬件机器部署Kubernetes
  3. 云服务商对你而言太过高昂,你接受使用云主机,以降低可用性、运行效率为代价换取性价比
  4. ......

总之,如果你有充分的理由,请往下进行;

否则,我非常建议你使用Kubernetes云服务商。它虽然贵,但是很省心。

准备工作

服务器

要按照本文方法部署Kubernetes,你需要准备服务器:

  1. 至少两台服务器
    1. 一台用作Master:至少2G内存
    2. 一台用作Worker:至少2G内存
      • 这只是可运行配置,一般而言,运行微服务应用,你可能要为Worker提升配置,且多准备几台Worker
  2. 这些服务器可以是硬件服务器、也可以是vps
  3. 如果需要执行高强度数据操作(比如数据库查询),请保证这些机器处于同一内网(如果你使用vps的话)

应用

你需要准备能够随时工作于生产环境的Kubernetes应用,包括应用镜像和deployment文件。

知识

你需要对Kubernetes和Linux操作有基本了解。

本文内容

本文介绍以下内容

  1. 配置Docker和容器运行时
  2. 安装Kubeadm
  3. 创建kubernetes集群
  4. 配置持久存储
  5. 配置ingress(公网访问)
  6. 配置https

本文不会展开以下内容

  1. 将Kubernetes部署到云服务商
  2. 如何编写Kubernetes应用
  3. 如何编写Kubernetes deployment

演示操作环境

  1. 使用Debian12作为所有机器的操作系统
  2. 所有机器是vps,Master和Worker不在同一内网,但Worker彼此在同一内网
    • Master是阿里云ecs
    • Worker是国外vps

准备工作

重启所有机器

某些镜像在第一次重启之前可能存在问题。如果你的机器从来没重启过,我建议先重启一遍。

bash 复制代码
reboot now

网络连通性

非常重要!

在开始前,先检测各个机器之间的连接性。

然后检查所有机器的端口开启情况。某些防火墙或者云服务器会默认关闭端口,你需要确保对应机器的以下端口是可以公开访问的:

Master结点

Protocol Direction Port Range Purpose Used By
TCP Inbound 6443 Kubernetes API server All
TCP Inbound 2379-2380 etcd server client API kube-apiserver, etcd
TCP Inbound 10250 Kubelet API Self, Control plane
TCP Inbound 10259 kube-scheduler Self
TCP Inbound 10257 kube-controller-manager Self

Worker结点

Protocol Direction Port Range Purpose Used By
TCP Inbound 10250 Kubelet API Self, Control plane
TCP Inbound 30000-32767 NodePort Services (default) All

详情请见Kubernetes文档 Ports and Protocols

提示:

  1. 为了避免意外,我的建议是先开放全部端口,搞定了再关闭不需要的端口。
  2. 请仔细检查防火墙设置,并且尝试在服务器之间测试tcp连接

你可以跳过这步,但如果出了问题,请不要忘记排查防火墙/安全组。

提供一个检测tcp连接的方法:

A主机作为服务端,开启端口的服务

bash 复制代码
nc -l -p 6443

B主机作为客户端,接通端口

bash 复制代码
nc <address-of-A> 6443

发点东西过去看看A能不能收到

关闭swap

Kubernetes暂时无法直接运行于开启交换区(虚拟内存)的环境,因此请确保你关闭了所有交换区。

查看是否有交换区(swap容量不为0):

bash 复制代码
free -h

关闭交换区直到关机/重启:

bash 复制代码
swapoff -a

禁止开机启动交换区:

打开以下文件

bash 复制代码
vim /etc/fstab

将里面所有交换区注释掉,示意:

bash 复制代码
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/vda1	/               ext4	 defaults	1      1
# /dev/vda2	swap	        swap	 defaults       0       0

同步所有服务器上的时间

如果服务器的时间不准确,可能会造成证书过期,CoreDNS失联等问题。

所以请务必保证所有服务器处于相同时区,并且与时间服务器同步时间。

设置系统时区

bash 复制代码
timedatectl set-timezone Asia/Shanghai

设置本地时钟使用UTC时间

bash 复制代码
timedatectl set-local-rtc 0

重启相关服务

bash 复制代码
systemctl restart rsyslog
systemctl restart crond

更改服务器的主机名称(非必须)

服务器的主机名称(hostname)将成为Kubernetes的结点名称。

名称无法(很难)在部署后更改,请在部署前决定每个机器的名称。

建议:

  1. 主机名应识别出是Master还是Worker
  2. 如果你的服务器数量很少,你可以使用数字作为结点后缀
  3. 如果服务器很多,而且每个配置不同,建议附加简洁的配置信息,或者用途
  4. 总之你需要一眼就能认出这是哪个服务器
  5. 主机名绝对不能重复

如何更改

vps

如果你是云服务商的vps机器,先看看控制面板能不能改

硬件服务器

如果不能改或者硬件主机,按以下操作:

修改主机名

bash 复制代码
hostnamectl set-hostname <your-host-name>

修改hosts文件

bash 复制代码
vi /etc/hosts

将里面出现的所有旧名称项,都替换为新主机名

注意: 某些主机的hosts文件会有托管,此时直接修改hosts无效。请注意hosts文件是否有相关注释说明。

重新登陆ssh就可以看到新主机名

安装docker和containerd容器运行时

这一步需要在Master和Worker进行

请参考官方教程进行安装

按照教程安装完后,你应该会同时安装完docker和containerd

验证安装

验证docker和containerd成功安装并启动:

bash 复制代码
systemctl status docker
systemctl status containerd

配置containerd

此步具有时效性,只保证编写时刻(2023-11-5)需要如此操作。

  1. containerd安装时默认关闭cri(容器运行时)插件,需要手动打开
  2. 另外kuberadm需要使用cgroup v2,因此需要开启containerd的cgroup v2支持

打开/etc/containerd/config.toml文件,作两处修改

bash 复制代码
vi /etc/containerd/config.toml
  1. cridisabled_plugins中移除
bash 复制代码
# disabled_plugins = ["cri"];
disabled_plugins = [];
  1. 在文件中加入以下内容以启用cgroup v2
bash 复制代码
version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".containerd]
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            SystemdCgroup = true

编辑完毕后重启服务

bash 复制代码
systemctl restart containerd

安装kubeadm

这一步需要在Master和Worker进行

请参考官方教程进行安装

验证安装

验证已经安装kubeadm、kubelet和kubectl

bash 复制代码
kubeadm
systemctl status kubelet
kubectl

配置vpc网络的外部访问

这步仅需要在满足以下条件的机器上执行:

  1. 机器希望以公网身份接入网络
  2. 机器网卡的IP并不是机器的公网IP

检测公网IP是否挂载在网卡上

请输入以下命令检测默认网卡是否为公网IP

bash 复制代码
ip route get 1.1.1.1 | grep -oP 'src \K\S+'

或者使用以下命令,观察默认网卡(比如eth0)是否存在你的公网IP

bash 复制代码
ip a

如果显示的是你的公网IP,或者你不需要公网接入,你可以跳过这一节;否则,继续往下执行。

创建公网IP的虚拟网卡

输入以下命令,为默认网卡增加临时的IP地址。这里假设你的默认网卡名为eth0,如果不是,你需要修改

bash 复制代码
ifconfig eth0:0 <公网IP>/24 up

这将持续到机器重启,重启后你需要重新创建。

关于持久化......每个镜像使用的网络服务不同,会有不同的搞法,这里就不展开了

就单纯给点提示吧:

  1. 确定自己系统使用的是什么网络服务
bash 复制代码
systemctl list-unit-files | grep -i network
  1. 谷歌一下这个东西怎么增加IP地址

验证IP是否生效

使用以下命令,检测公网IP是否出现在你的默认网卡中

bash 复制代码
ip a

修改kubeadm启动kubelet时使用的ip

查看kubelet的环境变量文件:

bash 复制代码
systemctl cat kubelet.service | grep EnvironmentFile

你会看到类似以下的输出:

bash 复制代码
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
EnvironmentFile=-/etc/default/kubelet

kubeadm官方说第一个文件是自动生成的,不建议修改。因此我们修改第二个(不存在则创建)

bash 复制代码
vi /etc/default/kubelet

在打开的文件中,指定--node-ip参数为公网IP

bash 复制代码
KUBELET_EXTRA_ARGS=--node-ip=<公网IP>

关于这点的更多阅读资料

初始化Kubernetes Master

这一步只需要在Master进行

中途出现问题可能会导致kubeadm初始化中断,你可以使用kubeadm reset重置到初始前状态

配置仓库镜像

这步需要在无法连接到k8s.gcr.io的Master执行(大陆机器)

如果是国内环境,很遗憾,无法访问默认的k8s.gcr.io容器仓库。

没有关系😭,我们可以使用镜像仓库

此处需要将容器仓库修改为国内镜像

如果你想了解更多,请参考这里

配置containerd sandbox-image的镜像

我们使用阿里云作为镜像服务器

打开containerd配置文件

bash 复制代码
vi /etc/containerd/config.toml

在文件中加入以下结点:

注意,如果文件中有相同的结点,将sandbox_image附加至下面即可,不要创建重复结点。

bash 复制代码
version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"

初始化

使用适当的参数初始化kubernetes:

  1. --pod-network-cidr是设置后面使用的flannel网络插件的网段。
    • 如果你按照本文操作,必填。
  2. --image-repository: 使用阿里云镜像
    • 如果机器无法访问k8s.gcr.io需要填写。
  3. --apiserver-advertise-address: 指定API Server的监听地址。
    • 如果你的机器处于vpc网络(公网IP不在网卡上),且你希望支持公网访问,需要在创建了虚拟网卡的基础上,填写公网IP。
  4. 其它可配置参数请看官方文档

指令示意:

bash 复制代码
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers \
--apiserver-advertise-address=<公网IP>

启动集群

在上一步顺利完成后,会显示以下信息:

bash 复制代码
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

......

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join <ip-address>:6443 --token nrlc17.k0ypuceqs0nmv6i2 \
	--discovery-token-ca-cert-hash sha256:c8fe4911959673b6392f46abd89d744de86584225202396cd5f13aec76be53b0

然而无论你是否为root用户,都请按照这个提示启动kubernetes集群:

bash 复制代码
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

而且请记住最后输出的加入token,workers需要使用这个加入集群。(如果没记住也不慌,后文教你重新生成)

验证集群状态

验证状态:

bash 复制代码
kubectl cluster-info
bash 复制代码
Kubernetes control plane is running at https://<ip-address>:6443
CoreDNS is running at https://<ip-address>:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

观察结点状态:

bash 复制代码
kubectl get node
bash 复制代码
NAME       STATUS     ROLES           AGE     VERSION
master-1   NotReady   control-plane   4m46s   v1.28.3

此处NoReady是正常现象,因为我们还没安装网络插件

存档点!

恭喜你已经成功渡过第一阶段,你还好吗😊?

接下来需要进行kubernetes内部的配置,在此之前我需要做一些提醒:

如何排错

在接下来的过程中,由于环境问题你可能会出现各种各样的状态异常、容器Crash。本文不可能涵盖所有的情况,但我会给你解决问题的方向。

出现这种问题是你应该:

  1. 首先使用kubectl get pods -A -o wide等命令查看出错的容器。
    1. 使用kubectl describe pod <pod-name> -n <namespace>查看错误的信息
    2. 使用kubectl logs <pod-name> -n <namespace>查看容器日志
  2. 得到错误信息后,请善用搜索引擎和社区解决问题
  3. 如果你使用的国内的服务器,建议你也查看国内社区的信息。因为这可能涉及到主机服务商虚拟化技术不同导致的,就如上面提到的阿里;也有可能说是某种不可抗力导致的,比如......

做好准备

因为下一步"安装网络插件"对环境是破坏性的。如果安装失败了,它很难简单使用kubeadm reset重置。它会给你的机器引入永久性的改变......

我想说的是,如果网络插件安装失败、或者想改变使用的网络插件,你可能会陷入一种"无论如何配置都不行"的境地。

你仍然可以反复尝试,但我需要提醒的是:如果你多次尝试都失败,很可能是机器环境已经被网络插件改变过了,你可能需要回滚这些设置。

所以:

  1. 如果你有条件,可以考虑在此时创建一个系统镜像,以方便回滚。
  2. 如果不行,你可以查找如何彻底卸载网络插件的方法, 这个东西因插件、版本而异。
  3. 但较简单的做法,可能是重装系统,然后重新执行一次上述步骤。

部分地区网络连接问题

下面的步骤我们需要拉取各种容器镜像。

由于部分镜像需要在registry.k8s.ioquay.io下载。然而这几个源在天朝强大的防火墙下已经无法访问了。

不同于hub.docker.com,这些网站是无法通过docker配置镜像解决的......

得益于此,要拉取镜像,你需要化简为繁:

  1. 寻找一个镜像站
  2. 从镜像站下载镜像
  3. 将下载的镜像tag为registry.k8s.io/xxx...
  4. 修改部署的yaml文件,将每个ImagePullPolicy修改成Always以外的值,使其使用本地镜像

至此,我是完全投降了......

虽然不是不能做,而是实在太麻烦了!!!

以后还有其它需要registry.k8s.io/xxx...的容器怎么办,又重复这四步吗???

我的建议是,收手吧阿祖,外面全是防火墙😅

如果没有魔法 ,我建议你选一个只在hub.docker.com拉镜像的存储方案

因此,国内Worker机器的部署请自求多福,并没有一劳永逸的做法,这个东西会非常麻烦

下面的操作如果涉及内陆无法下载的源,我会尽量提醒

好了,该说的就这么多了,祝你好运!

安装网络插件(flannel)

这一步只需要在Master进行

coredns服务需要网络插件支持。

kubernetes支持许多网络插件,这里使用flannel

关于选择哪个网络插件

并不是所有网络插件都适用于你的网络环境。

因为机器的网络环境不同,某些网络插件可能不支持你的环境。

比较老牌的是calico,但是如果是阿里云等vpc网络环境(公网IP不在网卡),目前(2023-11-8)的calico很难在公网正常运作。

因此我选择了flannel。

这并不代表flannel是最好的。我想说的是:如果你(我😅)折腾一个网络插件很久,重装多很多次都不行,也有可能是该插件本身不适用于你的网络环境。不妨换个试试。

部署flannel

如果你不需要进行额外配置,直接下载部署即可:

bash 复制代码
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

如果需要改变默认配置,你可以先wget下来,修改后再apply。

等待所有pod安装完毕

bash 复制代码
watch kubectl get pods -A -o wide

ctrl+c退出

验证node准备完毕

bash 复制代码
kubectl get nodes
bash 复制代码
NAME       STATUS   ROLES           AGE   VERSION
master-1   Ready    control-plane   40m   v1.28.3

Worker加入集群

这一步只需要在每个Worker进行

生成连接串

如果你没有记录连接串,或者连接串已过期,可以在Master结点使用以下命令创建连接串:

bash 复制代码
kubeadm token create --print-join-command

加入集群

使用生成的join命令加入集群:

bash 复制代码
kubeadm join <ip-address>:6443 --token zuyxod.5yglqucae2hejoqj --discovery-token-ca-cert-hash sha256:ff5ab3bcc2eb8aa9b4fb17eee028359fce1799984282cca01d4960a3a84369ce

配置持久存储(Persistent Volume)

如果你的应用需要存储状态(使用数据库等)需要执行

选择哪种持久存储?

Kubernetes的持久存储卷 (Persistent Volume,下简称PV) 可以大致分为两类:

  • 网络存储
  • 本地存储

一般使用网络存储,因为它与pod所在的node无关。无论pod被部署到哪个结点,它都能访问到位于网络的持久存储卷。

但是如果你的应用需要高强度IO,你可能需要使用本地存储。Kubernetes本地存储插件主要有两个,生产环境中请使用local而非hostPath
local虽然比hostPath更好,但也有明显的缺点:

  1. pod会被绑定至存储所在的node
  2. 必须手动分配空间,无法使用动态空间分配

所以请权衡性能与高可用性。

对于内网环境中,一般情况下使用网络存储并不会有明显的IO瓶颈。因此本文配置网络存储。

数据冗余

存储冗余可以保证数据不会丢失。

冗余可以产生在两个层面:

  1. 存储层面:可以在将文件写入到PV时就将数据存储多份
  2. 应用层面:比如镜像数据库和分片数据库

在存储层进行冗余并不是没有缺点,它会导致占用的存储空间成倍增长。

而且存储层的冗余并不能带来数据的高可用性。因为如果你只有一个数据库实例,在这个数据库宕机时,服务仍然无法访问(当然数据是不会丢失)。

所以如果你打算为高可用而组建分布式数据库,存在多个数据库实例,其实数据库本身就给你产生了冗余。此时在底层再次冗余只是浪费空间。

是否需要在PV层存储冗余?

对PV而言,本节主要讨论你是否需要在PV层面进行冗余。

现在,假设你的数据非常宝贵,你不希望丢失数据。

你打算将数据存储到自己的服务器中,而且你至少存在两个Worker,否则白搭。

  1. 如果你使用的是裸机组网,并且裸机的存储不存在RAID,即底层不存在任何冗余。那你需要在PV层使用冗余。
  2. 如果你使用的是云服务主机,并且你确信云服务主机已经为你提供了底层冗余,即只要它不跑路,你的数据不可能丢。那你不必在PV层使用冗余。
  3. 如果你打算在应用层(数据库)对数据进行冗余,此时在底层冗余也是不必要的。

安装持久存储

云原生存储

云原生存储是专为容器环境打造的存储方案。在Kubernetes中,推荐使用云原生存储,而非直接操作文件系统。

云原生存储带来的好处是:

  1. 它本身也部署于Kubernetes中,比较方便
  2. 它为你管理、分配存储资源
  3. 大部分方案提供备份、数据恢复、数据冗余等功能
云原生存储一览

目前比较火的有这些:

  1. OpenEBS
  2. Rook
  3. GlusterFS
  4. Longhorn

自己选一个吧

配置OpenEBS

**注意:**OpenEBS的部分镜像源无法在内陆访问,请自行权衡

我选择安装OpenEBS,当然你可以选择其它的

安装iSCSI

这一步需要在每个需要OpenEBS分配空间的机器上执行

如果你使用默认的配置安装,那OpenEBS会在所有Worker上分配

OpenEBS需要iSCSI支持,这个需要安装在系统上,不同系统的安装方法不一,可参考这里

在debian中,你可以使用以下命令检测iSCSI是否启动:

bash 复制代码
systemctl status iscsid

如果找到服务,并且启动,你可以跳到下一步

如果找到服务,但已关闭,请启动:

bash 复制代码
systemctl enable --now iscsid

如果没找到服务,请安装:

bash 复制代码
apt-get install open-iscsi
systemctl enable --now iscsid

最后,检测服务确实正常运行:

bash 复制代码
systemctl status iscsid
部署OpenEBS

官网指示安装OpenEBS Operator以及需要的存储支持

OpenEBS提供两种存储方案:

  1. 本地存储
  2. 镜像重复存储(远程存储)

首先查看默认的安装给你装了什么?

bash 复制代码
kubectl get pods -n openebs -o wide | grep operator

如果没出现你想要的engine,你需要自己安装:

两种方案均有多种存储引擎可供选择,你可以根据自己的需求安装。

比如,你需要使用远程存储,你可以安装cStorJivaMayastor中的一个。

使用Jiva引擎建立持久存储

我这里安装Jiva作为远程存储引擎

安装的具体步骤请参考官网指示

主要做三个事情:

  1. 安装Jiva引擎
  2. 配置Jiva
    • 关于Jiva的镜像重复数(replicationFactor),请按需求填写,一般不超过安装了OpenEBS的Worker数
  3. 创建使用Jiva的Storage Class

提醒:创建完Policy和Storage Class记得apply

bash 复制代码
kubectl apply -f <jiva-policy>
kubectl apply -f <jiva-storage-class>

最后,检测storage class正确创建:

bash 复制代码
kubectl get sc

输出应该存在刚刚创建的storage class:

bash 复制代码
NAME                  PROVISIONER           RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
openebs-device        openebs.io/local      Delete          WaitForFirstConsumer   false                  3h46m
openebs-hostpath      openebs.io/local      Delete          WaitForFirstConsumer   false                  3h46m
openebs-jiva-csi-sc   jiva.csi.openebs.io   Delete          Immediate              true                   6s

由于Jiva Storage Class是支持动态提供(Dynamic Provisioning)的,因此我们不需要手动创建PV

设置默认存储类(可选)

设置默认存储类可以使其它应用在不指明存储类的时候也能使用持久存储。非常建议为集群设置一个默认存储类。

但这并不是必须的

默认存储类会在kubectl get sc的输出中显示default字样。按我们现在的做法,是没有默认存储类的(见上节输出)

我们可以使用以下指令设置/取消一个默认存储类:

bash 复制代码
kubectl patch storageclass <storage-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

注意<storage-class-name>填写存储类的名字;最后的布尔值控制是设置还是取消。

设置后再次检测storage class的情况,输出应如下:

bash 复制代码
NAME                            PROVISIONER           RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
openebs-device                  openebs.io/local      Delete          WaitForFirstConsumer   false                  3h54m
openebs-hostpath                openebs.io/local      Delete          WaitForFirstConsumer   false                  3h54m
openebs-jiva-csi-sc (default)   jiva.csi.openebs.io   Delete          Immediate              true                   8m30s

配置外网入口

如果你需要支持公网访问,你需要进行这一步

本节将使用ingress nginx controller配置公网访问。

安装ingress nginx controller

安装ingress非常简单,根据官方教程,使用deployment安装:

bash 复制代码
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

输入以下命令等待容器创建完毕

bash 复制代码
watch kubectl get pods -n ingress-nginx

最后你会看到类似这样的结果,只要看到controller为Running就是正常:

bash 复制代码
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-6vlrc        0/1     Completed   0          5m12s
ingress-nginx-admission-patch-7wvqp         0/1     Completed   1          5m12s
ingress-nginx-controller-8558859656-zskdq   1/1     Running     0          5m12s

解决无法访问

此时查看支持ingress正常运作的LoadBalancer服务:

bash 复制代码
kubectl get svc -n ingress-nginx

你会发现输出如下,EXTERNAL-IP永远为pending

bash 复制代码
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.15.62   <pending>     80:32503/TCP,443:30116/TCP   7m18s
ingress-nginx-controller-admission   ClusterIP      10.97.192.44   <none>        443/TCP                      7m18s

随之你会发现ingress nginx controller无法外部访问......

这是因为自建Kubernetes与云环境不同:自建集群并不自带负载均衡器实现,如果你此时创建LoadBalancer,将永远处于pending状态。

对此我们有两种做法:

  1. 安装负载均衡实现
  2. 不使用负载均衡直接访问

下面将介绍这两种做法,注意,并不是所有做法都适合你的情况,请注意阅读后续提及的先决条件和弊端。

**注意:**总共有3种做法,选择1种执行即可。

这些做法出自ingress nginx官方文档

安装负载均衡

方法1:MetalLB

裸机配置首选!!!

该方法只适合 所有结点(至少是所有Worker结点)处于同一内网。

也就是:硬件裸机,或者某些支持组内网的云服务商。(但我发现某些国外的vps商若处于同一机房也能用)

首先,请按照官方指南安装metallb

然后,配置IP池。最简单且内网通用的做法是使用Layer 2(数据链路)模式

你只需要将你的公网IP段加入IP池就好。

简单来说就是把你希望作为外网访问入口的IP段加入到IP池,MetalLB会将这些IP依次分配给出现的LoadBalancer

网段支持子网写法和范围写法

如果你的服务器只有单个IP,可以视为只有单个IP的子网<ip-address>/32

写法示意如下:

yaml 复制代码
  addresses:
  - 123.123.10.0/24
  - 123.123.9.1-123.123.9.5
  - fc00:f853:0ccd:e799::/124
  - 123.123.11.10/32

由于LoadBalancer是交通枢纽。如果你的服务器各自有流量限制,请尽量选择流量充足的服务器写进IP池。

因为MetalLB版本迭代非常快,因此这里也不放具体的配置了,请按照官方指南进行配置。


错误排查

你可能会遇到webhook错误,请尝试将MetalLB的Controller转移到Master结点。

错误原因不明,详情请参考这个issue


执行结束后,输入以下命令可以查看nginx ingress的LoadBalancer IP:

bash 复制代码
kubectl get svc -n ingress-nginx

如无意外,此时LoadBalancer显示的将是一个公网IP。

bash 复制代码
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.15.62   <Public-IP>    80:32503/TCP,443:30116/TCP   21m
ingress-nginx-controller-admission   ClusterIP      10.97.192.44   <none>         443/TCP                      21m
方法2:外部LoadBalancer

效仿云服务,我们也可以自己整一个外部负载均衡器。这个负载均衡器可以是硬件或软件。

软件负载均衡器,推荐使用HAProxy

但这个方法我没试,看起来非常专业,有兴趣的朋友可以配置一下😘

直接连接

方法3:暴露主机网络

我们也可以不使用任何LoadBalancer,ingress nginx controller提供了直接对外访问的做法。

首先我们将部署文件下载下来:

bash 复制代码
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

在里面找到kind: Deployment的项,加入以下配置项(注意不要引入重复的项):

yaml 复制代码
template:
  spec:
    hostNetwork: true

保存退出后,将其应用:

bash 复制代码
kubectl apply -f deploy.yaml

此时检查nginx controller的情况:

bash 复制代码
kubectl get pods -n ingress-nginx -o wide

你会发现nginx controller的IP变成了pod所在node的公网IP(前提是你的node真的有公网IP)

bash 复制代码
NAME                                        READY   STATUS      RESTARTS   AGE    IP             NODE           NOMINATED NODE   READINESS GATES
ingress-nginx-admission-create-6vlrc        0/1     Completed   0          140m   10.244.7.11    rn-l-3500m7t   <none>           <none>
ingress-nginx-admission-patch-7wvqp         0/1     Completed   1          140m   10.244.6.13    rn-m-2g4t      <none>           <none>
ingress-nginx-controller-7f8dd58d99-s9qmg   1/1     Running     0          50s    <public-ip>    rn-m-2g4t      <none>           <none>

注意事项:

  1. 每个node最多只能运行一个nginx controller,因为它使用到了宿主机的资源。你无法将同一端口分配给两个nginx实例。
  2. 此时nginx controller的IP地址取决于其运行的node,这个IP可能并不是你所期望的。

那么怎么指定访问nginx的公网IP呢?

只能间接指定,有两个做法:

做法1:使用结点选择/亲和性配置,让nginx controller优先创建于你指定的node

首先为希望运行ingress的node创建一个typeingress的标签(你可以更改为任意的键值对)

bash 复制代码
kubectl label nodes <name_of_your_node> type=ingress

在部署nginx controller前,修改Deployment

在里面找到kind: Deployment的项,加入以下配置项(注意不要引入重复的项):

yaml 复制代码
template:
  spec:
    nodeSelector:
      type: ingress

修改完后重新apply


做法2:使用daemon set部署nginx controller,让每个node(包括Master)都运行nginx controller

- 如果你希望健壮一点,可能这个比较好
- 当然它的运行开销也更大

验证外网访问

使用上步暴露的公网IP(各个做法不同),在外部浏览器中尝试进行访问。

如果显示nginx 404页面,说明配置正确。

部署你的APP

恭喜,到这一步你已经配置好生产环境下使用的Kubernetes了

接下来你只需要:

  1. 将你的APP部署到Kubernetes
  2. 编写Ingress文件将流量从ingress nginx转发到应用中
    • 请参考ingress nginx官方文档
    • 注意:如果你使用IP访问,请不要 指明spec.rules.host;如果使用域名访问,请务必指明
  3. 访问自己的应用,确认已正常运作

配置域名和https

非必须,单如果是公开访问的应用,建议进行

这一步你需要准备:

  1. 一个解析至你应用的域名
  2. 一个合法的TLS证书

首先将证书内容复制到服务器,比如我的:

bash 复制代码
root@master-1:~/cert# ls
mysite.com.crt	mysite.com.key

创建一个包含证书内容的secret

bash 复制代码
kubectl create secret tls <your-secret-name> --cert=mysite.com.crt --key=mysite.com.key

修改你的ingress(nginx)部署文件,加入以下结点:

yaml 复制代码
spec:
  tls:
    - hosts:
        - mysite.com
      secretName: your-secret-name # 这个是上步的secret名称
  rules:
    - host: mysite.com # 站点名必须对应,否则返回404

重新apply

使用https://mysite.com访问你的站点,确认TLS证书已生效

通关

大功告成!至此我们终于成功自建了Kubernetes网络

这一路上并不容易,但这仅仅只是一个开始

你还需要关注如何进行维护、备份和迁移......

但到此我有一个疑问

Docker、Kubernetes的出现不是为了简化应用部署的过程吗?

但搞起来怎么这么复杂呢?

程序员的工作是降低了,但运维真就哭了......

但无所谓,反正我这种oneman从来都是一个人全部搞定,四舍五入没节省任何时间😭


好吧,牢骚是发到这......

其实Kubernetes部署到部分云服务商还是很舒服的,只是我看到那个价格就已经饱了

所以自建Kubernetes虽然能达到生产环境的要求,但需要额外的维护!

除非你真的有充分的理由,否则云服务商还是一个不错的选择啊(除了很贵😅)

相关推荐
€☞扫地僧☜€1 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
全能全知者2 小时前
docker快速安装与配置mongoDB
mongodb·docker·容器
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
ZHOU西口6 小时前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
牛角上的男孩6 小时前
Istio Gateway发布服务
云原生·gateway·istio
JuiceFS8 小时前
好未来:多云环境下基于 JuiceFS 建设低运维模型仓库
运维·云原生
景天科技苑8 小时前
【云原生开发】K8S多集群资源管理平台架构设计
云原生·容器·kubernetes·k8s·云原生开发·k8s管理系统
wclass-zhengge9 小时前
K8S篇(基本介绍)
云原生·容器·kubernetes
颜淡慕潇9 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
川石课堂软件测试11 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana