分类 Docker & K8S 下的文章

这部分其实并不是 rancher 的内容,在我们之前的文章中,我们需要上传我们的可执行文件到虚拟机上,目前仅有两个 woker 节点,这样每一个节点进行上传其实还好,但如果节点变成了 10 个,20 个甚至更多的话,那么将会消耗大量的时间,虽然上产环境中基本上没有这么玩的。:)

我们完全可以利用 NFS 来共享我们的主机目录,这样一来,这样一来,我们只需要向一个节点上传文件即可。

# 安装 nfs-utils
$ yum install nfs-utils -y

# 编辑配置文件
$ sudo vim /etc/sysconfig/nfs

LOCKD_TCPPORT=30001 # TCP 端口占用
LOCKD_UDPPORT=30002 # UDP 端口占用
MOUNTD_PORT=30003 # 挂在使用端口
STATD_PORT=30004 #状态使用端口

# 启动服务
$ systemctl restart rpcbind
$ systemctl restart nfs-server
$ systemctl enable nfs-server
$ systemctl enable rpcbind

# 创建需要共享的目录
$ mkdir -p /data/nfs/

# 编写共享配置文件
$ vim /etc/exports
/data/nfs  192.168.0.0/24(rw,async)
# 重启服务
$ systemctl restart nfs-server

#查看挂载是否生效
$ showmount -e localhost
Export list for localhost:
/data/nfs 192.168.0.0/24

接下来,我们将 goapi 的可执行文件上传到到服务器上。

# 先在服务器上创建myapp
$ mkdir myapp
# 本地上传
$ scp -p build/myserverv2 [email protected]:/data/nfs/myapp/

在其他机台主机上执行如下命令

$ yum install nfs-utils -y

$ showmount -e 192.168.0.211
Export list for 192.168.0.211:
/data/nfs 192.168.0.0/24

$ mkdir -p /data/nfs
$ mount -t nfs 192.168.0.211:/data/nfs /data/nfs

$ tree /data/nfs/
/data/nfs/
└── myapp
    └── myserverv2

这样一来,我们的文件就共享出来了。

在 ClusterIP 模式下应用只能在集群内部进行访问,外部(公网)无法访问它,例如 MySQL,Redis 这些服务,外网是不许要进行访问的,这个时候就可以选择这个模式。

现在我们来模拟这个场景,例如现在访问 goapi.maksim.wbesite 现在显示的是 hellword,如果我们想在内网增加一个技术服务,显示一共有多少人访问了当前接口。

package main

import (
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
    "sync"
)

type Counter struct {
    counter int
    sync.Mutex
}

func NewCounter() *Counter {
    return &Counter{counter: 0}
}
func (receiver *Counter) Add() {
    receiver.Lock()
    defer receiver.Unlock()
    receiver.counter = receiver.counter + 1
}

func (receiver *Counter) Get() int {
    receiver.Lock()
    defer receiver.Unlock()
    return receiver.counter

}

type Response struct {
    Count int
}

func main() {
    r := gin.Default()
    counter := NewCounter()
    r.Handle("GET", "/count", func(context *gin.Context) {
        counter.Add()
        context.JSON(http.StatusOK, &Response{Count: counter.Get()})
    })
    err := r.Run(":8080")

    if err != nil {
        log.Fatalln(err)
    }
}

代码非常简单,我们继续使用前面讲到的方法将其部署的 k8s 中,在这里就不赘述了,这里主要关注网络模式选择了集群IP,并且暴露的端口号是 80。

2023-03-04T13:49:46.png

当我们部署好后,可以看到访问端口显示的n/a。

2023-03-04T13:50:07.png

我们在 master 节点上利用 curl 访问 容器的IP。

2023-03-04T13:50:37.png

我们对容器进行重新部署,这个时候你会发现,ip 地址变了,我们不可能每一次重新部署后都去关注这个 ip 的变化,所以就需要利用到服务发现了。

2023-03-04T13:50:47.png

当我们创建好 workloads 后,其实 rancher 已经帮助我们创建好了服务发现,我们点击服务发现。

2023-03-04T13:51:01.png

在其中我们可以看到 counterservice。

在这里简单说一下服务发现的机制,在 Rancher 2.4 版本以后使用 k8s-coredns 作为服务发现的基础, serivce 后会自动增加一条解析记录,如果在同一 namespace 下,我们直接可以在容器内访问 counterservice。不同空间需要利用 service_name.namespace 进行访问。

2023-03-04T13:51:14.png

我们进入my-go-api 容器中可以试验一下。

2023-03-04T13:51:34.png

这一步需要安装 curl 工具,具体方法请自查百度。这样一来就证明可以通讯了,接下来,我们改造 my-go-api。

r.Handle("GET", "/", func(context *gin.Context) {
        host := context.Query("host")
        if host == "" {
            context.JSON(400, gin.H{"error": "no host!"})
            return
        }
        resp, err := http.Get("http://" + host + "/count")
        if err != nil {
            context.JSON(400, gin.H{"error": err.Error()})
            return
        }
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            context.JSON(400, gin.H{"error": err.Error()})
            return
        }
        context.String(200, "hello world!"+string(body))
    })

我们来接收一个 host,这个 host 就是 service 名字,将改造后的代码上传至服务器,重新部署。

2023-03-04T13:51:54.png

我们直接可以点击克隆,然后新建一个叫做 demo 的命名空间,并且将名称改为counterservice2。

2023-03-04T13:52:08.png

2023-03-04T13:52:21.png

2023-03-04T13:52:31.png

当我们在浏览器内输入 counterservce2 的时候会发现报错了。

2023-03-04T13:52:40.png

我们现在输入 counterservce2.demo 就可以访问另外一个命名空间下的项目了

2023-03-04T13:53:01.png

这是服务发现的基本机制,如果不需要做健康检查的话,我们完全可以直接用 k8s 来做服务发现。

ingress 暴露服务

在上一篇中,我们部署了一个 Go 应用,在这一小节中,我们将增加其副本并且做负载均衡。

在最开始我们一共有三台节点,其中一台跑 Rancher,一台 Master 和一台 Node,既然是负载,那肯定是需要将 pod 运行在两个节点上,所以我们需要新增一个主机节点,由于我的电脑内存即将吃紧所以对其配置进行了调整。

机器ip配置备注
CentOS7192.168.0.1992cpu 4G ramRancher
CentOS7192.168.0.2102cpu 4G ramMaster
CentOS7192.168.0.2112cpu 2G ramNode
CentOS7192.168.0.2121cpu 2G ramNode

我的那一台电脑一共是有 16G 已经无法再增加节点了,所以我们为了模拟多节点,我们只能通过削减其他虚拟机的配置空闲出来一些资源,然后新建一个虚拟机。

我们重复第一章的内容,然后将其加入节点。

2023-03-04T12:00:55.png

我们找到集群,然后点击编辑,在页面的最下方,还有增加节点的方法。

2023-03-04T12:01:05.png

2023-03-04T12:01:13.png

当皆甜增加完成后,我们来调整在上一章节中部署的 goapi 应用。

2023-03-04T12:01:26.png

我们 Deployment 部署位 2 个 Pods,同时将端口映射修改为了 NodePort

2023-03-04T12:01:36.png

我们可以看到在两个节点上都调度了 pod,但是出现了报错,这是因为在 worker 宿主机上没有我们设置的目录,我们只需要将可执行文件以相同位置上传上去即可,后续更好的解决方案肯定是 CICD,我会在后面的章节进行讲解。

2023-03-04T12:01:55.png

上传完成后,很快就完成了部署。

2023-03-04T12:02:05.png

由于节点选择的事 NodePort 我们只能通过节点的 IP 地址来对应用进行访问,这很明显不是我们想要的效果,我们一般对外提供服务都是使用域名的形式,然后通过反向代理进行负载均衡到我们的后台计算节点。

在 k8s 中如果想要实现这一效果我们就需要使用 ingress 对外暴露我们的服务。

Ingress 相当于一个 7 层的负载均衡,我们可以理解为是一个反向代理并定义规则的一个 api 对象。Ingress Controller 通过监听 ingress api 转换为各自的配置(常用的有 nginx-ingress,trafik-ingress)。

我们点击负载均衡,然后添加规则。

2023-03-04T12:02:15.png
2023-03-04T12:02:35.png

k8s 为我们提供了一个域名后缀,但是我们通常不会使用,我们来使用自定义域名,其中端口号就是我们要暴露的 80 的端口,我们对外访问的时候,选择的也是这个。

由于是在虚拟集中创建的集群,所以我们绑定域名最简单的方案是修改本地 host 文件,域名直接指向任意一个 worker 节点(Ingress 通过边缘节点对外提供服务,所以如果在生产环境,建议利用 keeplived + lvs 对外提供服务,避免单点故障)。

# maksim.website vitralbox
192.168.0.211 goapi.maksim.website

新件号 ingress 后我们等待完成状态。

2023-03-04T12:02:50.png

2023-03-04T12:02:58.png

2023-03-04T12:03:07.png

我们特意将 woker2 节点上的代码进行了修改,为的就是能够演示出 worker 的样子。

配置 SSL 证书

我们的线上服务一般都会有配置证书的需求,在 Rancher 中配置证书非常容易,我们只需要在资源中选择密文,在选择证书,添加证书即可。

2023-03-05T08:16:05.png

这里需要注意,我们需要进入到 project 下才可以看到资源文件夹。

2023-03-05T08:16:15.png

作用域我们需要注意,这里选择为整个项目或者单个命名空间生效,在这里,我们选择了 myweb。

证书的神情,可以到阿里云免费的 SSL 证书。

添加完证书后,我们修改 goapi 的负载均衡,找到 SSL/TLS 证书选项卡,点击添加证书。

2023-03-05T08:16:33.png

2023-03-05T08:16:51.png

选择我们的证书后,填写对应的域名。点击保存。

2023-03-05T08:15:21.png

当我们访问后就会发现证书已经生效了,在浏览器中显示以增强安全性。

路径重写

当我们要去做路径重写的时候,其实只需要做两项配置即可,如果在 nginx 中我们还需要修改配置文件,然后在 reload,其实 k8s 中的重写和 nginx 基本一致,因为其底层使用的就是 nginx。

例如我们的 goapi这个项目,如果我们想通过 https://goapi.maksim.website/api 来访问我们的接口。我们可以像下面这样操作。

2023-03-05T08:31:25.png

我们先是增加重写规则,表示将 /api/转发到项目上 (.*)代表了分组,也就是我们的重写部分,然后在标签和注释中增加注释。

2023-03-05T08:31:51.png

nginx.ingress.kubernetes.io/rewrite-target = /$1

我们去访问项目。

2023-03-05T08:32:03.png

这个时候我们发现已经是 404 状态了,增加 /api/ 再来看一下。

2023-03-05T08:32:12.png

项目可以访问了。

我们要部署的程序很简单,我们使用 gin 对外提供一个接口返回 helloworld。

2023-03-04T09:50:41.png

然后利用交叉编译生成 go 的执行文件。

2023-03-04T09:51:19.png

我们在 ide 中点击开始按钮后,自动的帮我们生成了myserver可执行文件,我们将这个文件上传到 work 节点的 /data/k8s-volume/myapp 目录下。

scp build/myserver [email protected]:/data/k8s-volume/myapp

2023-03-04T09:51:33.png

接下来,我们来部署 GoAPI,我们还是点击部署工作负载。

2023-03-04T09:51:45.png

我们现将 workloads 的基础信息填写上。

2023-03-04T09:52:04.png

这一次我们选择 HostPort 直接将容器的端口与所在节点的端口进行映射,这种模式下和我们 docker -p xx:xx 的效果是一样的。

和 docker 一样,我们也可以将容器内的目录映射到宿主机上,在这里我们选择主机映射。

2023-03-04T09:52:23.png

通过配置,我们将容器 /app 目录下的内容映射到我们的宿主机上,目前我们只有一个 work 节点。

因为我们是吧运行程序上传到了服务器上,所以我们需要利用 k8s 来帮助我们的容器。

2023-03-04T09:52:33.png

这一步其实就相当于我们的 docker run。

Untitled

点击部署后,我们需要等待片刻。

如果部署失败,我们可以点进工程内进行查看失败原因。

2023-03-04T09:52:56.png

在事件中显示了失败原因,在拉取apline 的时候出现失败。原来是我们的镜像名写错了,那么修改镜像名称。

2023-03-04T09:53:10.png

修改后,将会新的 pod 来替换老 pod。

2023-03-04T09:53:23.png

好了,现在已经部署成功了,我们来进行访问。

2023-03-04T09:53:35.png
2023-03-04T09:53:48.png

我们再来尝试,192.168.0.210:8080 能否访问。

2023-03-04T09:54:12.png

我们可以看到 Master 节点是不会转发这次请求的,这就是 NodePort 和 HostPort 的区别。

本文的目的是帮助开发者快速了解 基于 Rancher 的 k8s 管理以及部署工作,我们将介绍 Rancher2.x 的基本使用,目前暂不涉及运维层面,请不要将这个文章中所涉及的内容直接搬运到生产环境,毕竟部署起来和稳定运行时两回事,

k8s 本身部署还是很复杂的,部署起来会碰到很多坑,如果使用 Rancher 那就特别简单了,即使你不去特意学习 k8s 也能部署。

先说一下环境,我们先准备三台虚拟主机,如果有钱的话,也可以买两台服务器,我这里使用的是 17 年生成的小米 Pro 笔记本,I7 处理器,16G 内存,已经满足了试验环境的搭建。

机器ip配置备注
CentOS7192.168.0.1992cpu 4G ramRancher
CentOS7192.168.0.2102cpu 4G ramMaster
CentOS7192.168.0.2112cpu 4G ramNode

这里建议一台机器专门跑 Rancher ,跑 k8s 的节点,最好配4G 以上的内存,要不然会很卡。这三台节点,需要内网可以进行访问。

我们接下来开始准备工作,在每个节点上分别执行下面的命令

#0. 安装必备软件
yum -y install wget net-tools nfs-utils lrzsz gcc gcc-c++ make cmake libxml2-devel openssl-devel curl curl-devel unzip sudo ntp libaio-devel wget vim ncurses-devel autoconf automake zlib-devel  python-devel epel-release openssh-server socat  ipvsadm conntrack ntpdate
#1. 关闭防火墙
systemctl stop firewalld && systemctl disable firewalld
#2. 关闭 SElinux
sed -i  's/SELINUX=enforcing/SELINUX=disabled/'  /etc/sysconfig/selinux
sed -i  's/SELINUX=enforcing/SELINUX=disabled/g'  /etc/selinux/config
#3. 关闭 swap
sed -i 's/.*swap.*/#&/' /etc/fstab
#4. 重启
reboot -f
#5. 安装 docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
#6. 启动并且设置自启动
systemctl enable docker
systemctl start docker

#7. 配置阿里云代理,这里我隐藏了自己的,这东西免费,自己可以去阿里云进行申请。
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://xxxxx.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
# 8.编辑时间同步,要不然 etcd 可能会报错,很麻烦
crontab -e
 
* */1 * * * /usr/sbin/ntpdate   cn.pool.ntp.org

在 rancher 虚拟机上部署单节点 rancher。


# 创建目录
mkdir -p /data/rancher
# 创建 docker 容器
docker run -d --restart=unless-stopped -p 8080:80 -p 8843:443 -v /data/rancher:/var/lib/rancher rancher/rancher:v2.4.5
在这里,我们使用的 rancher 单节点模式,如果是用于生产,请使用高可用的 rancher,要不然 rancher 也是有可能出现故障的。
我这里使用的 2.4.5 版本,这是因为我在工作和学习中使用的都是这个版本,就目前文章截止最新版本应该是已经到了 2.6。

启动时我们需要等待一会。我们可以用 docker logs -f 命令来监控日志输出。

访问https://192.168.0.199:8843/ 即可看到这个页面。

2023-03-04T08:07:31.png

我们设置自己的密码,就会跳转到主页,接下来,我们创建集群。

2023-03-04T08:07:57.png

我们在右下角可以更改系统语言。

2023-03-04T08:08:07.png

接下来,我们点击添加集群。

2023-03-04T08:08:50.png

Rancher 不光可以帮助我们建立新的集群,还可以在已有集群的基础上进行导入。

2023-03-04T08:08:23.png

在这里,我们选择自定义后填写集群名称,直接点击下一步,我们使用默认配置即可。

2023-03-04T08:09:29.png

2023-03-04T08:09:42.png

如果是增加 Master 节点,那么这三个我们都需要勾上。

这里需要注意,复制的时候要把 localhost 改成能够进行通信的 ip。

sudo docker run -d --privileged --restart=unless-stopped --net=host -v /etc/kubernetes:/etc/kubernetes -v /var/run:/var/run rancher/rancher-agent:v2.4.5 --server https://192.168.0.106:8843 --token 8bgdphz9mk9xbbdkrhvsqh6v5zh9gkc55276w5l7m8xlwkgr8xmz4w --ca-checksum 3db552d02da1618d971233431766d57485777d558395dbba6dc2c185ca3bc1b8 --etcd --controlplane

2023-03-04T08:10:04.png

我们分别添加完成后需要等待片刻,如果中途卡在 etcd 上,可以尝试下面的方法。

docker stop $(docker ps -aq)
docker system prune -f
docker volume rm $(docker volume ls -q)
docker image rm $(docker image ls -q)
rm -rf /etc/ceph \
       /etc/cni \
       /etc/kubernetes \
       /opt/cni \
       /opt/rke \
       /run/secrets/kubernetes.io \
       /run/calico \
       /run/flannel \
       /var/lib/calico \
       /var/lib/etcd \
       /var/lib/cni \
       /var/lib/kubelet \
       /var/lib/rancher/rke/log \
       /var/log/containers \
       /var/log/pods \
       /var/run/calico

这块我第一次安装的时候折腾了将近两个小时。

2023-03-04T08:10:17.png

当环境没有问题后,我们来部署一个 nginx,首先我们来创建一个项目

2023-03-04T08:10:39.png

2023-03-04T08:10:49.png

我们可以看到有几个默认的 Project,在这里我们就需要来了解一下什么是 namespace 了。

Namespace 是对一组资源和对象的抽象几何,用来将系统内部的对象划分为不同的项目组或者用户组。常用来隔离不同的用户,比如 k8s 自带的服务一般都运行在 kube-system namespace中。

2023-03-04T08:10:59.png

我们先创建一个项目。

2023-03-04T08:12:55.png

接着再增加一个 namespace,之后在左上角我们就可以进入到自己的项目了。

2023-03-04T08:11:41.png
2023-03-04T08:11:49.png

在这里我们可以创建四个内容:Workloads,Load Balancing, Service Discovery,Volumes。

workloads

工作负载是在 Kubernetes 上运行的应用程序。

在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成, 你都可以在一组 Pod 中运行它。 在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组 容器 的集合。

2023-03-04T07:55:54.png

Pod 是所有业务类型的基础,也是 k8s 管理的最小单位,可以理解为它是一个或多个容器的组合。

接下来,我们利用 workloads 来创建 nginx。

在这里我们先使用 NodePort,这代表在所有节点(虚拟机)上开放一个特定的端口,任何发送到该端口的流量都被转发到对应的服务上,端口范围胡思 30000~32767。

2023-03-04T07:55:41.png

2023-03-04T07:55:27.png

当显示 Active 的时候就证明已经部署好了,我们可以到 work 节点查看一下。

2023-03-04T07:55:06.png

访问 ip 查看是否能够访问。

2023-03-04T07:54:49.png

如果说访问不了可以尝试一下解决方案

# 安装 iptables
yum install iptables-services -y
# 关闭 iptables 防火墙
service iptables stop   && systemctl disable iptables

# 修改内核参数
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 生效配置
sysctl --system

#设置网桥包 iptables

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 >/proc/sys/net/bridge/bridge-nf-call-ip6tables

echo """
vm.swappiness = 0
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
""" > /etc/sysctl.conf

#生效配置
sysctl -p

#开启ipvs,不开启ipvs将会使用iptables,但是效率低,所以官网推荐需要开通ipvs内核
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
ipvs_modules="ip_vs ip_vs_lc ip_vs_wlc ip_vs_rr ip_vs_wrr ip_vs_lblc ip_vs_lblcr ip_vs_dh ip_vs_sh ip_vs_fo ip_vs_nq ip_vs_sed ip_vs_ftp nf_conntrack"
for kernel_module in \${ipvs_modules}; do
 /sbin/modinfo -F filename \${kernel_module} > /dev/null 2>&1
 if [ $? -eq 0 ]; then
 /sbin/modprobe \${kernel_module}
 fi
done
EOF

chmod 755 /etc/sysconfig/modules/ipvs.modules && bash
/etc/sysconfig/modules/ipvs.modules && lsmod | grep ip_vs