前言
本文着重在 ECS 的使用实践,面向个人开发者或者是小型团队。借着这次评测的机会,期望把一些个人实践分享出来供大家交流,全文篇幅较长,涉及到的面也会广一些,如果你正计划学习 Kubernetes ,我期望本文对你入门 Kubernetes 生态会有一定的实践帮助,让我们看一下新一代性能怪兽 ECS 可以激起多大的浪花🌊 。
这里我画了一下本文的结构图,结构相对比较清晰,我们将基于 ECS 进行扩展,包括存储计算分离、应用容器级别的高弹性、基于 Terraform 的云资源操作等,并结合现有成熟的 SaaS 服务。基于 ECS 构建出一整套高弹性、可水平扩展、多方位监控等特性的集群。
由于社区右侧目录只能支持到三级, 三四级目录会造成同级显示,在此处我会对文章整体的结构进行一个概括,方便您有一个大概的了解:
- Part1: 对 ECS 选型以及性能测试
- Part2: 准备阶段,会基于单台的 ECS 进行一些初始化配置讲解
- Part3: 部署 K3s 底座,其中包括存储(自建/NAS)以及负载组件以及常用中间件的部署
- Part4: 实践场景,包括结合云效构建个人博客,构建个人云端 IDE,团队协作办公组件部署等场景
- Part5: 计算节点的添加,包括手动部署、通过 terraform 操作、ECS 结合 ECI 等场景
- Part6: 全方位监控告警,包括应用、机器监控等
- Part7: 总结
服务器选型/测试
选型
这里我们选用的是测评给到的机器 c5 服务器,2c4G,公网带宽 1M,操作系统的话按照习惯重新安装的 Ubuntu 20.04。
挑选 vps 需要考虑到地域,网络规划等等方面,没有比官方文档更合适的了,大家可以参考选型的最佳实践。
- 根据个人的经验,如果考虑到作为开发者个人的云端工具库,可以选
计算型 C5 2c4G
规格长期使用,可以应付大部分的场景。 - 如果是有一些临时需要测试的场景需要大量的机器(比如作为中转机,测试弹性以及大数据的场景),我一般选择
按量付费
-》C7 计算型 2c4G/4c8G
的规格,可以随用随释放,基本可以满足个人的测试场景,并且费用不会很高。或者是结合 ECI 进行使用(后续有测试案例)。
性能测试
我们通过 bench.sh 来进行简单的 io 以及 网络测试
- 平均 io 在 106MB/s 这个只是一个简单的参考标准
- 在国内下载速度以及延迟维持在一个很低的值
wget-qO- bench.sh | bash
如果需要更加详细的基准测试指标,可以使用 byte-unixbench, byte-unixbench 提供了更加详细的指标,直接使用镜像运行测试非常方便。
由于 byte-unixbench 是一个基准,测试结果会受到硬件操作系统等等的影响(可以参考 README),感兴趣的小伙伴可以按需测试。
可以从实例管理中看到,ECS 挂载的是高效磁盘,IOPS 给出的标准是 1960。
我们通过 fio 对磁盘进行一下测试,编写 fio 配置文件,我们分别对 随机读,随机写,顺序写 进行测试
cat<<EOF>config.fio[global]filename=/dev/vdaioengine=libaiodirect=1bs=4kiodepth=16runtime=60threadgroup_reporting[randread]rw=randreadnumjobs=5stonewall[randwrite]rw=randwritesize=1Gnumjobs=5stonewall[write]rw=writesize=1Gnumjobs=5stonewallEOF
执行等待测试完成,由于报告过长,这里只展示部分信息,具体的测试配置大家可以参考官方文档。
fio config.fio
可以看到在三组测试中,IOPS 都与云盘规格相同。
fio-3.16 Starting 15 threads Jobs: 5(f=5): [_(10),W(5)][27.7%][w=11.1MiB/s][w=2852 IOPS][eta 07m:50s] randread: (groupid=0, jobs=5): err=0: pid=2358774: Mon Apr 1821:55:36 2022 read: IOPS=1975, BW=7901KiB/s (8091kB/s)(463MiB/60048msec) ... randwrite: (groupid=1, jobs=5): err=0: pid=2360284: Mon Apr 1821:55:36 2022write: IOPS=1971, BW=7885KiB/s (8074kB/s)(463MiB/60083msec); 0 zone resets ... write: (groupid=2, jobs=5): err=0: pid=2361794: Mon Apr 1821:55:36 2022write: IOPS=2546, BW=9.95MiB/s (10.4MB/s)(597MiB/60032msec); 0 zone resets ... Run status group 0 (all jobs): READ: bw=7901KiB/s (8091kB/s), 7901KiB/s-7901KiB/s (8091kB/s-8091kB/s), io=463MiB (486MB), run=60048-60048msecRun status group 1 (all jobs): WRITE: bw=7885KiB/s (8074kB/s), 7885KiB/s-7885KiB/s (8074kB/s-8074kB/s), io=463MiB (485MB), run=60083-60083msecRun status group 2 (all jobs): WRITE: bw=9.95MiB/s (10.4MB/s), 9.95MiB/s-9.95MiB/s (10.4MB/s-10.4MB/s), io=597MiB (626MB), run=60032-60032msecDisk stats (read/write): vda: ios=118714/272163, merge=0/610, ticks=4795956/9664295, in_queue=13892048, util=97.79%
准备阶段
初始化
首先我们登陆到服务器,修改 ssh 配置 禁止使用密码登陆, 将本地公钥添加到 authorized_keys。
ssh key 可以通过 ssh-keygen 生成,默认都会生成到 用户目录的 .ssh 下。
vim /etc/ssh/sshd_config # 修改内容见下图service sshd restartecho"ssh-rsa 替换成本地的公钥" >> ~/.ssh/authorized_keys
使用密钥登陆可以优雅的避免被暴力破解,同样不用担心本地的 ssh 密钥被重置掉,ecs 提供了 vnc 链接的方式让我们可以直接操作。
更改服务器 hostname,这里我按照 node-内网IP
的规则命名。
hostnamectl set-hostname node-172019221084
安全组放行端口,我们计划使用 ingress 作为流量入口,所以放行 80/443 即可,详细操作可以参考 添加安全组规则。
部署 k3s 底座
为什么是 k3s?
需要回答这个问题,首先要说一下为什么容器化?最主要的一点容器化保证了服务运行环境的一致性,正因为这一点,大大推动了 devops 的发展,让跨环境、灰度发布等都十分轻松,使用容器让我们在 ECS 上更加的快捷方便。
其次再来回答为什么是 kubernetes,对于个人开发者/中小型企业来说,每年都会有云厂商的福利,低价买到一些云服务器。使用kubernetes 可以轻松的将新服务器作为计算节点加入到我们的个人集群,跨云厂商也毫无压力。再加上云厂商的 SaaS 服务有很多的免费额度,比如云效的流水线,阿里云的容器镜像仓库等,足够我们开发者或者是小型的团队使用,服务器仅用于发布应用或者跑一些中间件即可。
而 k3s 作为 kubernetes 轻量发行版,占用资源极少,对于不太充足的服务器资源是很好的选择。当然如果资源充足的情况下,强烈建议在 ECS 部署高可用 kubernetes 或者是上 ACK。
部署 k3s
安装 wireguard
apt-get update apt install -y wireguard
执行如下命令安装,参数解释:
参数 |
解释 |
K3S_EXTERNAL_IP |
ECS 的公网ip, 可以在实例详情中查看 |
INSTALL_K3S_MIRROR |
cn,指定使用国内源 |
INSTALL_K3S_EXEC |
--tls-san: 证书的 san,根据是否需要公网访问 apiserver 来按需选择 --flannel-backend:使用 wireguard 作为后端,方便我们打通其他服务器,具体请参考 --kube-proxy-arg:kubeproxy 配置,我们选用 ipvs 模式 --disable:k3s 默认会安装 traefik 作为 ingress controller,这里我选择自己安装 nginx ingress controller |
exportK3S_EXTERNAL_IP="you external ip"exportINSTALL_K3S_MIRROR=cn exportINSTALL_K3S_EXEC="--write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666 --tls-san $K3S_EXTERNAL_IP--node-external-ip $K3S_EXTERNAL_IP--flannel-backend wireguard--kube-proxy-arg proxy-mode=ipvs masquerade-all=true metrics-bind-address=0.0.0.0--disable traefik,servicelb"curl-sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | sh-
执行安装后,我们可以通过 kubectl
来查看节点状态
(可选) 如果有卸载或者重装的需求,可以直接执行
k3s-uninstall.sh
安装 helm
helm 作为 kubernetes 生态的应用管理工具,大部分的开源项目都有提供 chart 包,可以方便的进行应用管理,可以算得上是 kubernetes 应用管理的 “瑞士军刀”。
通过如下命令安装 helm3
wget https://get.helm.sh/helm-v3.8.1-linux-amd64.tar.gz tar -zxvf helm-v3.8.1-linux-amd64.tar.gz mv linux-amd64/helm /usr/local/bin && chmod+x /usr/local/bin/helm
部署存储
考虑到我们后续会有其他的节点加入进来,为了让我们的计算跟存储可以分离,方便后续计算节点的扩充,服务可以“漂起来”,我们需要部署 CSI 插件,这里推荐如下几种:
- 如果手里有 NAS/云盘 等资源,强烈建议作为存储,可以做到计算与存储的分离,只需要部署 alibaba-cloud-csi-driver ,对接 NAS/云盘 即可(OSS 不支持通过 StorageClass 动态管理 pvc)。
- 使用 NFS,我们需要自己手动搭建一个 nfs-server,然后使用 nfs-subdir-external-provisioner 对接即可。
- 使用 GlusterFS,基于
glusterfs
的 kadalu 来作为分布式存储解决方案,kadalu 可以让我们无需关心glusterfs
安装,而且非常轻量。 - 其他方案:openbs, longhorn,rook,ceph 等等
上述方案大部分都会通过 StorageClass 来管理 pvc,后续部署只需要声明使用的 sc 即可,我们这里选择前三种来实操下。
nas
nas 可以按使用量付费,存储价格也很便宜,如果没有特殊要求,非常建议搭配 ECS 使用。
首先我们部署 csi-driver,等待组件启动完成
git clone https://github.com/kubernetes-sigs/alibaba-cloud-csi-driver.git && cd alibaba-cloud-csi-driver kubectl apply -f deploy/rbac.yaml kubectl apply -f ./deploy/nas
购买 NAS,这里我们选择容量型,地区与 ECS 一致,其他的按需配置
进入到 NAS 管理页面,我们根据 ECS 所在的专有网络以及交换机,配置挂载点
挂载点配置完成后,我们回到 ECS 编写 StorageClass 配置
- 其中 server 配置就是上图中我们的挂载点,我们采用 subpath 的方式
- 后面的 /data 就是我们指定的目录,所有 pv 的目录都在该文件夹下
cat <<EOF > alicloud-nas-sp-sc.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: alicloud-nas-sp mountOptions: - nolock,tcp,noresvport -vers=3parameters: volumeAs: subpath server: "your_mountpoint_address:/data" archiveOnDelete: "false"provisioner: nasplugin.csi.alibabacloud.com reclaimPolicy: Delete EOF
创建 sc
kubectl apply -f alicloud-nas-sp-sc.yaml
此时我们使用项目中自带的 demo 做一下测试,可以看到 nas 成功绑定,具体存储的 path 如下所示
kubectl apply -f ./examples/nas/dynamic/pvc-subpath.yaml
kadalu
执行如下命令进行安装
curl-fsSL https://github.com/kadalu/kadalu/releases/latest/download/install.sh | sudobash-xkubectl kadalu install
等待组件安装完成
这时候我们创建个 data 目录,并且作为集群的存储。考虑到是个人开发环境,这里我们一个节点存储数据即可,如果要求稳定至少请至少上三节点或者使用 NAS。
mkdir-p /data/storage-pool-1 kubectl kadalu storage-add storage-pool-1 --path node-172019221084:/data/storage-pool-1
这时候查看我们的 storage class 已经创建出来了。后续可以直接使用该 sc
kubectl get sc
到这里我们的 glusterfs 存储就部署完成了
nfs
首先我们部署 nfs-server
apt-get install nfs-kernel-server -y# (可选)如果非 nfs server 节点,其他的 ECS 节点都需要安装 nfs clientapt install nfs-common -y
创建存储目录,并配置访问权限
mkdir /data/nfs chmod go+w /data/nfs echo"/data/nfs *(rw,sync)" >> /etc/exports systemctl restart nfs-server
如果有其他的 ECS 节点需要访问 nfs,这里我们在安全组中限制只有节点之间可以访问 nfs-server 的端口, 或者推荐直接限制来源,放行给其他节点所有端口。
添加 repo 仓库
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm repo update
编写 values.yaml, 指定 nfs-server 的端口以及路径
cat <<EOF > nfs-csi-values.yaml image: repository: registry.cn-shanghai.aliyuncs.com/viper/nfs-subdir-external-provisioner nfs: server: your_ecs_ip path: /data/nfs EOF
执行安装即可
helm upgrade --install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -f nfs-csi-values.yaml -n nfs-provisioner-system --create-namespace
安装完成后,可以看到对应的 StorageClass 已经存在,后续使用即可
部署 ingress controller
k3s 以及 存储我们已经部署完成,接下来我们部署 ingress controller 来作为我们整个集群的流量入口,这里选择的是通过 helm 安装 nginx ingress controller。
添加 ingress-nginx 的 repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update
编写我们的 values 文件,我们采用 Daemonset 方式部署,由于我这边没有备案的域名,我们选择流量入口为 1080 以及 1443
cat <<EOF > ingress-nginx-values.yaml controller: image: registry: registry.cn-shanghai.aliyuncs.com/viper image: ingress-nginx-controller digest: sha256:bc30cb296e7548162afd9601f6b96261dcca8263e05b962694d1686b4d5a9584 extraArgs: https-port: 1443 http-port: 1080 watchIngressWithoutClass: true hostNetwork: true kind: DaemonSet nodeSelector: ingress: "true" admissionWebhooks: patch: image: registry: registry.cn-shanghai.aliyuncs.com/viper image: kube-webhook-certgen digest: sha256:78351fc9d9b5f835e0809921c029208faeb7fbb6dc2d3b0d1db0a6584195cfed EOF
我们将当前 ECS 的节点打上 ingress 标签,作为集群流量入口
kubectl label no node-172019221084 ingress=true
部署 ingress-controller,等待部署完成。
helm install ingress-nginx ingress-nginx/ingress-nginx --version=4.0.8 -f ingress-nginx-values.yaml -n ingress-nginx --create-namespace
部署完成后,如果访问需要 https,我们需要利用 acme.sh 来配合 阿里云 dns 实现泛域名证书的自动续期,这里就不做赘述了,官方文档写得非常详细,可以创建 ak/sk 利用 dns_ali 实现对泛域名的自动续期。
这里有个需要注意的地方是,我们需要结合 acme.sh 生成的证书来更新到 secret 中,可以在定时任务中增加如下部分,将 secret 更新。
kubectl delete secret ingress-tls -n ingress-nginx kubectl create secret generic ingress-tls --from-file=tls.crt=generic-cert.pem --from-file=tls.key=generic-key.pem -n ingress-nginx
(可选)在 ingress 的 values.yaml 中声明 secret 名称,如下配置所示。
controller extraArgs default-ssl-certificate"ingress-nginx/ingress-tls"
部署常见中间件
我们可以部署一些常见的中间件,比如 MySQL(建议购买 rds,活动的时候真的太香了),Redis,ETCD 等,这里我们部署一个开发用的 mysql。
添加 repo
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update
部署 mysql,这里我们 storage class 使用我们上面部署的存储
helm install mysql bitnami/mysql -n addons-system --create-namespace--set global.storageClass="kadalu.storage-pool-1"
部署完成后,可以根据提示查看默认 root 密码
此时我们可以查看下监控数据,不得不说 ECS 真的太强了
实践场景
场景1: 部署博客
这里我们结合云效与阿里云镜像仓库来部署我们的个人博客,实例代码请参考 hugo-quickstart,案例中我们对博客这个应用添加了 ingress,并设定了域名。
首先我们创建一条流水线,选择部署到 Kubernetes,其中集群链接我们可以登陆在 ECS 中执行如下命令查看,其中的 server 地址换成我们的公网入口
cat ~/.kube/config
执行流水线,构建并上传镜像到阿里云镜像仓库,通过我们代码仓库中的 manifests/manifests.yaml 部署到我们的 ECS。
此时可以登录到集群中查看是否部署成功
kubectl get po -n blog-system
我们此时根据配置的域名进行一下做一下域名解析并访问,比如 blog.xxxx:1080, 可以看到我们的博客已经部署成功了。
我们可以在云效中提供的 webhook 配置到 github 的 workflow 中,通过提交代码自动触发流水线构建发布到 ECS,这里就不做演示了,之后计划专门有一篇文章介绍 github workflow。
场景2: 打造个人云端开发环境
开源产品 code-server,可以直接在浏览器中使用,我们来将 code-server 部署到我们的 ECS 中。
首先克隆 coder-server 代码
git clone https://github.com/coder/code-server
编写配置文件,这里我们指定域名以及存储(使用之前部署的 kadalu 或者其他 csi)
cat <<EOF > coder-values.yaml ingress: enabled: true hosts: - host: "coder.your.domain" paths: - / persistence: storageClass: kadalu.storage-pool-1 size: 1G EOF
安装,等待 coder namespace 下所有组件起来
helm install coder code-server/ci/helm-chart -f coder-values.yaml --namespace coder --create-namespace
根据提示我们获取初始密码
此时我们访问配置的域名,输入上述密码
熟悉的 vscode 展现在我们面前了,coder 也是目前大多数厂商在线 IDE 的基石,此时我们可以登录 github 账号同步并且安装一些常见的插件,比如 数据库管理,Kubernetes支持,nocalhost 等。
除了开发过程在 coder 以外,可以将代码、个人知识库、CI/CD 全部托管到云效上,完美的利用好现有的 SaaS 资源,才是真的做到应用生在云上,长在云上。
场景3: 私有化云上办公
在该场景中,我们将基于 nextcloud + onlyoffice 组合,实现云上办公套件,可以实现私有化云盘、office系统处理等等。需要注意的是如果该场景需要添加节点,大家可以参考本文下一章节 计算节点添加
部分,扩充节点,话不多说我们现在开始。
部署 nextcloud
首先我们部署 nextcloud,nextcloud 的 chart 包在本人写时还是存在一些问题的,比如使用 local 以外的 provisoner 存在问题:issue。所以我们还是按照 helm 使用流程,添加 repo 编写 values.yaml。
helm repo add nextcloud https://nextcloud.github.io/helm/ helm repo update
参数解释:
- ingress:开启 ingress 访问,我们nextcloud对外暴露,后面部署的 onlyoffice作为集群内服务。
- affinity:由于使用 local 存储,我们利用亲和性选择 app/nextcloud=true 的节点。
- internalDatabase:我们这里不使用默认的 sqlite,使用外部 mysql,需要禁用,否则即使启用外部数据库,仍然会用 sqlite。当然你也可以直接使用默认的 sqlite。
- nextcloud:配置 ingress 域名,默认管理员信息,php 配置等
- externalDatabase:这里我们使用上面部署的 mysql,提前创建好数据库。目前虽然官方支持 postgresql,不过在写该文章时候社区关于 external 链接 pg 的 issue 仍然没有关闭。
- 健康检查设置的失败次数多点,依赖外部数据库初始化时间较长。
- persistence:存储,这里我们 enable,由于是 k3s 这里的 local 的 storageClass 不用写,默认就是
local-path
- 其他比如邮箱服务等等,可以按需配置
cat <<EOF > nextcloud-values.yaml ingress: enabled: trueaffinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: app/nextcloud operator: Exists internalDatabase: enabled: falsenextcloud: host: your.domian username: your_custom_username password: your_custom_password configs: custom.config.php: |- <?php $CONFIG= array ( 'allow_local_remote_servers'=> 'true', ); externalDatabase: enabled: true type: mysql host: mysql.addon-system.svc.cluster.local database: nextcloud user: xxxxxx password: xxxxxx livenessProbe: failureThreshold: 600readinessProbe: failureThreshold: 600persistence: enabled: trueEOF
部署并选择一个节点打标
kubectl label no node-172019221084 app/nextcloud=truehelm upgrade --install nextcloud nextcloud/nextcloud -n office-system -f nextcloud-values.yaml
等待初始化完成即可,我们 dns 解析上述配置的域名,并且通过预设的账号密码登录。
登录过后我们进入到精选应用中下载安装并启用 OnlyOffice,用于后续与 documentserver 集成。
部署 documentserver
在 kubernetes 上部署 onlyoffice,是较为新的一个项目,这里我们先来按照该项目来尝尝鲜。大家同样的可以按需选择官方 docker 镜像直接编写 yaml 部署。
documentserver 依赖 rabbitmq,redis,postgresql,我们先来部署中间件,同样的放到 office-system 下。
添加 repo
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update
部署 rabbitmq
- rabbitmq 我们只需要指定 sc 即可,大家可以根据上面部署存储章节选择自己的 sc。
cat <<EOF > rabbitmq-values.yaml persistence: storageClass: kadalu.storage-pool-1 EOF
helm upgrade --install rabbitmq bitnami/rabbitmq -n office-system --create-namespace-f rabbitmq-values.yaml
部署 redis
- 这里 redis 我们跳过认证
cat <<EOF > redis-values.yaml global: storageClass: kadalu.storage-pool-1 architecture: standalone image: tag: 5.0.7-debian-10-r51 auth: enabled: falseEOF
helm upgrade --install redis bitnami/redis -n office-system --create-namespace-f redis-values.yaml
获取 documentserver 初始化 sql 并创建为 configmap
wget-O createdb.sql https://raw.githubusercontent.com/ONLYOFFICE/server/master/schema/postgresql/createdb.sql kubectl create configmap -n office-system init-db-scripts --from-file=./createdb.sql
部署 postgresql
- 声明默认初始化 sql
cat <<EOF > postgresql-values.yaml global: storageClass: kadalu.storage-pool-1 primary: initdb: scriptsConfigMap: init-db-scripts persistence: size: 8Gi auth: database: postgres EOF
helm install postgresql bitnami/postgresql -n office-system --create-namespace-f postgresql-values.yaml
这时候依赖的中间件就部署完成了,我们开始部署主角
- documentserver 默认 chart 就是按照 bitnami 一些参数作为默认值的,所以我们只需要 sc 即可
git clone https://github.com/ONLYOFFICE/Kubernetes-Docs.git
cat <<EOF > documentserver-values.yaml persistence: storageClass: kadalu.storage-pool-1 EOF
helm install document-server ./Kubernetes-Docs/ -n office-system -f documentserver-values.yaml
这里我们等待所有组件起来即可进行解下来的配置
nextcloud 集成 docuementserver
此时我们来到 nextcloud 来通过之前安装的 connector 集成 documentserver,在 设置中找到 ONLYOFFICE,配置 documentserver 的地址,由于我们两个组件都在同一个 namespace 下,可以直接通过 service 名称访问到。
- 其中的秘钥可以查看 jwt secret
kubectl get secret -n office-system jwt -ojsonpath="{.data.JWT_SECRET}" | base64 -d
- 这里我修改了一下用于 ONLYOFFICE DCOS 内部请求的服务器地址,默认的协议与端口不匹配。
配置完成后,如果校验成功会出现 onlyoffice 的个性化配置。
配置保存完成后,我们创建一个 excel,并且分享到其他用户,可以看到在线实时协同处理文档。一个基于 ECS 的轻量化办公协同杀手锏就完成了!
计算节点添加
上述几个部分介绍了 ECS 作为 k3s 中心节点的一些场景,接下来的部分我们将介绍一下 ECS 作为计算节点的添加,将从如下几个部分去介绍:
- ECS 直接部署 k3s-agent 接入到中心承载计算能力。
- 通过 Terraform 批量操作 ECS 资源,自动接入中心承载计算能力。
- 使用 ECI 作为计算节点,低成本超高弹性的云上计算能力。
ECS 部署 k3s-agent
我们重置该服务器(这里我选择的仍是 ubuntu 20.04)作为计算节点添加到其他的中心节点下。
首先还是需要重命名节点,这里按照我这边集群的命名风格
hostnamectl set-hostname node-alibaba-2
同样的我们安装 wireguard
apt-get update apt install -y wireguard
安装完成 wireguard 后,我们登录到中心节点,通过如下命令获取 agent 加入集群的 Token 凭证
cat /var/lib/rancher/k3s/server/node-token
接下来我们配置 k3s agent 的安装参数
参数 |
解释 |
K3S_TOKEN |
凭证,上个步骤获取的值 |
K3S_URL |
k3s apiserver 的公网地址,一般是中心节点公网地址的 6443 端口 |
INSTALL_K3S_MIRROR |
与中心参数解释一致 |
K3S_EXTERNAL_IP |
与中心参数解释一致 |
INSTALL_K3S_VERSION |
k3s 安装版本,这里选择与中心一致的版本 |
INSTALL_K3S_EXEC |
基本与中心部署参数一致,多了认证相关的,少了控制节点的参数 |
安装完成后,查看 k3s-agent 服务状态是否正常
service k3s-agent status
接下来到中心节点,查看集群的状态,可以看到 node-alibaba-2 节点已经加入到集群中了
kubectl get no
这里其实是有一个小坑的,flannel 在 k3s 集群公网之间的访问存在问题,可以参考 flannel-fixer 这个项目,部署到中心集群,会自动帮我们解决掉这个问题,原理也比较简单,通过 list/watch 节点资源,添加 flannel 的 annotation 到节点上,感兴趣的可以研究下。
(可选)如果有 agent 卸载重装的需求,可以执行如下命令
k3s-agent-uninstall.sh
通过 Terraform 批量添加
上一部分介绍的是如何在一台干净的 ECS 上去拉起我们的计算节点,操作方式过于“原始”了,对于 iaas 层资源的操作其实有更优雅的方式进行创建以及批量销毁,那就是 IaC (Infrastructure as Code)。
Terraform 目前算是 IaC 最常用的工具了,阿里云基本常见的资源都在 provider 中有提供,使用 terraform 在运维成本上会更加的有效,如果你没有接触过,希望本部分带你入门阿里云产品操作的新姿势。
安装并配置 terraform
可以通过链接访问并下载 terraform 的 cli,只需要一个二进制即可,这里我使用 brew 安装
brew install terraform
安装完成后我们需要三个环境变量,一套 AKSK,还有我们云产品所在的区域,这对于 alicloud provider 是必须的。
exportALICLOUD_ACCESS_KEY=xxxx exportALICLOUD_SECRET_KEY=xxxx exportALICLOUD_REGION="cn-wulanchabu"
我们来到 AKSK 管理,授权给子账号 ECS 以及 VPC 的所有权限
由于我测试是在
cn-wulanchabu
, 区域下没有 VPC,如果你已经在 Region 下有了 VPC 相关的配置,根据执行权限最小化的原则,只需要给只读权限就好。
编写资源描述
terraform 语法相对来说比较简单清晰,我们主要参考阿里云的 provider 文档来对资源的字段以及上下文传递操作。
我们先创建一个项目目录,用来存储 k3s-agent-install 相关的 terrform 配置。
首先创建一个文件 variables.tf
用来存储我们一些需要变动的变量,其中我们标识了镜像、区域、ecs 购买的类型、购买的数量等。
variable "instance_count" { default =2} variable "image_id" { default ="ubuntu_20_04_x64_20G_alibase_20220331.vhd"} variable "zone" { default ="cn-wulanchabu-c"} variable "ecs_type" { default ="ecs.t6-c2m1.large"}
接下来我们创建 terraform.tf
,来描述我们资源以及操作。
我们先来创建 vpc 相关的配置,这里创建 vpc,vswitch,安全组,安全组规则。
TIPS: 如果该区域内已经有通用配置好的 vpc 以及安全组等,直接跳过即可
provider "alicloud" {} resource "alicloud_vpc""vpc" { vpc_name ="tf_ecs_k3s_vpc" cidr_block ="172.16.0.0/12"} resource "alicloud_vswitch""vsw" { vpc_id = alicloud_vpc.vpc.id cidr_block ="172.16.0.0/21" zone_id = var.zone } resource "alicloud_security_group""default" { name ="default" vpc_id = alicloud_vpc.vpc.id } resource "alicloud_security_group_rule""allow_all_tcp" { type ="ingress" ip_protocol ="tcp" nic_type ="intranet" policy ="accept" port_range ="1/65535" priority =1 security_group_id = alicloud_security_group.default.id cidr_ip ="0.0.0.0/0"}
网络相关的配置准备完成后我们接下来配置密钥对,通过密钥对来访问操作服务器,我们先在本地生成密钥对,存储到 scripts 下,方便后续使用
mkdir scripts ssh-keygen
编写密钥对相关的描述,命名为 operator,并且指定公钥所在的路径
用到了 file() ,terraform 内置的文件读取函数
resource "alicloud_ecs_key_pair""operator" { key_pair_name ="operator" public_key = file("./scripts/id_rsa.pub") }
密钥对描述之后,解下来我们需要描述实例,这里我们描述了 ecs 的型号以及镜像,并且绑定了之前定义的 vpc 跟安全组。
这里我们没有对付费方式、使用的磁盘大小、内网IP、公网IP 进行描述,这些都有默认值的,可以在 apply 之前执行 terraform plan
查看,有些字段在实例创建后会自动生成。
TIPS:
- 上述提到可以跳过的 VPC 安全组配置,可以在该处指定
- internet_max_bandwidth_out 设置为 10 会自动生成 ECS 的公网 IP
resource "alicloud_instance""instance" { count = var.instance_count availability_zone = var.zone security_groups = alicloud_security_group.default.*.id instance_type = var.ecs_type system_disk_category ="cloud_efficiency" image_id = var.image_id vswitch_id = alicloud_vswitch.vsw.id internet_max_bandwidth_out =10}
既然 ECS 实例我们已经进行了描述,密钥对也进行了描述,那么他们两个需要一个媒介进行关联,那就是 alicloud_ecs_key_pair_attachment
资源,由于我们 instance 可能多个,这里我们通过 * 匹配
resource "alicloud_ecs_key_pair_attachment""attach" { key_pair_name = alicloud_ecs_key_pair.operator.key_pair_name instance_ids = alicloud_instance.instance.*.id }
我们对于云资源的描述基本就完成了,实例创建完成后需要初始化我们期望的是并且自动注册到我们的中心节点,alicloud_instance
中也提供了 user_data
字段,来让我们通过 cloud_init 的方式去在实例初始化时候执行脚本,这也是最优的一种选择。
但是对我们现在的实例来说,k3s-agent 在初始化过程中需要 public_ip
, 走 cloud_init 的方式并不是很合适。所以我们通过 remote-exec
或者是 ecs_command
的方式来搞定。
第一种方式是使用 provisioner 在初始化完成后执行 ssh 链接并执行初始化命令,我们利用到了 null_resource
来承载 provisioner,可以看到有如下三个部分:
- connection: 配置 ssh 链接,其中我们通过之前生成的 ssh key 来链接
- provisioner file: 将本地脚本拷贝到远端服务器,通过 connection 承载链接链路
- provisioner remote-exec: 执行远端命令
resource "null_resource""k3s-agent-install" { count = var.instance_count connection { type ="ssh" user ="root" private_key = file("./scripts/id_rsa") host ="${element(alicloud_instance.instance.*.public_ip, count.index)}" } provisioner "file" { content = format(file("./scripts/k3s-agent-install.sh"), "${element(alicloud_instance.instance.*.private_ip, count.index)}", "${element(alicloud_instance.instance.*.public_ip, count.index)}") destination ="/root/k3s-agent-install.sh" } provisioner "remote-exec" { inline = [ "chmod +x /root/k3s-agent-install.sh", "sh -c /root/k3s-agent-install.sh", ] } }
可以看到上述使用到了安装脚本,我们这边来配置下安装脚本,其中 private_ip
以及 public_ip
作为输入参数
TIPS: 需要注意下 format 的值,%s 是占位符,用到的 %需要转义 %%
apt-get update apt-get install wireguard -yhostnamectl set-hostname node-$(echo "%s" | awk -F. '{printf("%%03d%%03d%%03d%%03d",$1,$2,$3,$4)}')exportK3S_TOKEN=<your_k3s_token> exportK3S_URL=<your_k3s_server_path> exportINSTALL_K3S_MIRROR=cn exportK3S_EXTERNAL_IP=%s exportINSTALL_K3S_EXEC="--token $K3S_TOKEN --server $K3S_URL --node-external-ip $K3S_EXTERNAL_IP --node-ip $K3S_EXTERNAL_IP --kube-proxy-arg proxy-mode=ipvs masquerade-all=true --kube-proxy-arg metrics-bind-address=0.0.0.0"curl-sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | sh-
(可选)第二种方式是 ecs_command 的方式,我们可以通过如下方式来配置云助手的 ecs command,这样可以免密创建命令,实例创建完成后我们登陆到云助手执行命令即可。
resource "alicloud_ecs_command""k3s_install" { name ="tf-init-k3s" command_content = base64encode(format(file("./k3s-agent-install.sh"), alicloud_instance.instance.private_ip, alicloud_instance.instance.public_ip)) type ="RunShellScript" working_dir ="/root"}
执行部署及校验
编写完资源描述后,我们执行如下命令对于需要的模块初始化, 然后执行计划,对编写的配置进行校验,以及查看预期输出的值,上面我们配置的实例数量是 2,所以可以清晰的看到两个实例的执行计划
terraform init --upgradeterraform plan
执行 并确认, 可以看到资源在创建中,并且已经开始 ssh 进行初始化
terraform apply
此时我们从中心集群查看,节点已经成功注册上来,并且 ready 可以被调度了,全程自动化
此时我们查看一下云资源
如果节点不需要使用了,可以直接通过 terraform 一键销毁我们创建的资源
terraform destory
ECS 与 ECI 的碰撞
依托于 ECS 的超高稳定性以及强劲的性能,云上的弹性 ECI 应运而生,借着评测的机会我们将实操 ECS 部署的 K3s 集群接入 ECI。
首先我们来到 ECI ,创建 vNode,这里我们需要提供 vpc,vsw 以及 安全组相关信息,这里需要我们填写访问集群的 kubeconfig,可以从中心集群的 ~/.kube/config 获取。
填写 kubeconfig 时候注意 server 地址,这里我们填写内网地址,通过如下命令获取,解析来介绍如何添加路由。
kubectl get svc kubernetes
由于上面我们走的内网,需要添加一下路由,选择上述配置的专有网络,进入到自定义路由,把 apiserver 的 service 网段添加进去(不同方式搭建的 kubernetes 查找方式不同,k3s 默认就是 10.43.0.0/24 )
如果你期望 ECI 可以反向访问你的 Kubernetes 集群的话,可以将不同节点的路由,手动添加到路由表中,并且设置该主机作为下一跳,具体路由信息可以通过如下命令获取:
kubectl get no your_node -o yaml | grep-i podCIDR: | awk'{print $2}'
添加完成后我们提交 vnode 配置,可以在集群中看到 virtual-kubelet agent 已经 ready 了
接下来我们部署 eci-profile 组件,通过该组件我们可以编排 pod 调度到 eci 的规则以及规格等,参考官方文档,我们编写 eci-profile.yaml 并部署,等待组件起来。
kubectl apply -f eci-profile.yaml
从官方文档中我可以得到,默认 namespace 的筛选规则如下,只要 namespace 包含该 label pod 默认就会调度到 eci
我们此时编写一个简单的 demo,调度到 eci 上
apiVersion v1 kind Namespace metadata name eci-test labels alibabacloud.com/eci"true"---apiVersion apps/v1 kind Deployment metadata name gin-demo namespace eci-test spec replicas1 selector matchLabels app gin-demo template metadata labels app gin-demo name gin-demo spec containersimage registry-vpc.cn-shanghai.aliyuncs.com/viper/gin-demo v0.2 imagePullPolicy IfNotPresent name gin-demo ---apiVersion v1 kind Service metadata name gin-demo-service namespace eci-test spec portsport60000 targetPort60000 selector app gin-demo
部署过后,我们可以查看调度到的节点的确是 eci,并且可以在 eci 中查看到
ECI 只有在运行的时候按照占用的资源收费,费用很低很低,非常适合跑一些临时的任务或者应对一些弹性扩容的场景,如果你有一些有状态的服务想要跑上去,没问题的!可以参考文章上面介绍的基于 nas 的 csi-provisoner 部署,ECI 配 NAS 如鱼得水。
全方位监控告警体系建设
上面几个部分我们针对基于 ECS 构建的集群的配置以及应用部署进行了讲解,在本部分我们将在之前的基础上构建关于 应用/节点/云资源 监控告警。我们将基于 prometheus 生态技术栈入手,配合阿里云云监控体系构建高保障的体系。
Prometheus stack 部署
这里我们使用 prometheus stack 的包,里面包含了 prometheus、alertmanager、kube-state-metrics、grafana 等组件,可以免去非常多组件部署的烦恼。
首先我们添加 chart 仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update
接下来我们编写配置文件
参数 |
解释 |
prometheusOperator |
prometheus operator 相关配置,这里我用了 acr 中的镜像,chart 版本是 kube-prometheus-stack-35.0.3 ,其他版本需要自己替换 |
prometheus,alertmanager |
prometheus,alertmanager 相关配置我们只配置持久化即可,可以使用我们上面介绍的几种存储的任一 strorageClass, 这里我使用 |
grafana |
grafana 我们也将存储持久化,并且开放了公网地址 |
prometheusOperator admissionWebhooks patch image repository registry.cn-shanghai.aliyuncs.com/viper/kube-webhook-certgen kube-state-metrics image repository registry.cn-shanghai.aliyuncs.com/viper/kube-state-metricis prometheus prometheusSpec storageSpec volumeClaimTemplate spec storageClassName nfs-client accessModes"ReadWriteOnce" resources requests storage 50Gi alertmanager alertmanagerSpec storage volumeClaimTemplate spec storageClassName nfs-client accessModes"ReadWriteOnce" resources requests storage 50Gi grafana ingress enabledtrue hosts <your_domain> persistence enabledtrue storageClassName nfs-client
配置完成后,我们执行如下命令部署,所有配置我们放到了 monitor-system 下
helm upgrade --install prometheus-stack kube-prometheus-stack-35.0.3.tgz -n monitor-system --create-namespace-f values.yaml kubectl get po -n monitor-system
告警配置
部署完成后,我们通过如下命令获取 grafana 初始化密码
kubectl get secret -n monitor-system prometheus-stack-grafana -ojsonpath={'.data.admin-password'} | base64 -d
接下来我们访问 grafana,可以看到默认已经配置了从 ECS 机器监控,到 Kubnernetes 监控的模板
我们来看一下节点相关的数据,几乎覆盖了常见的机器指标
对于监控告警也是必不可少的,我们就以上面实践场景中的 blog
作为应用监控告警的案例,由于我们使用了 prometheus operator,所以我们需要创建 promrule 资源来描述告警规则
- 其中我们通过
PromQL
描述了blog-system
下的 pod,如果 cpu 已使用的值 / 配置的 cpu limit 值 超过了 90%,则触发告警 - 其中按照 namespace pod 的格式触发告警
apiVersion monitoring.coreos.com/v1 kind PrometheusRule metadata labels group applications release prometheus-stack app kube-prometheus-stack name prometheus-custom-applications.rules namespace monitor-system spec groupsname blog-rules rulesalert cpu_usage annotations description 应用 $labels.namespace / $labels.pod CPU 大于阈值 summary cpu 当前使用值 $value expr - 100 * max(rate(container_cpu_usage_seconds_total"blog-system" 5m )/ on (container, pod) kube_pod_container_resource_limits namespace="blog-system" resource="cpu" ) by (pod) > 90 namespace= for 1m labels application blog severity warning
部署以后我们可以通过如下命令查看已经存在的 prometheus 规则
TIPS: 可以看到默认 stack 中包含了非常多的基础告警规则
kubectl get promrule -n monitor-system
稍等一会等待同步,我们可以在 grafana 中的 Alert rules
查看到新增加的规则
TIPS: 这里默认对接的都是 prometheus 的数据源,同样可以登录到 prometheus 查看
(可选)配置钉钉告警,默认 alertmanager 是不支持钉钉告警的,我们可以通过 prometheus-webhook-dingtalk 作为转换层。
首先我们 clone 代码、配置并部署
git clone https://github.com/timonwong/prometheus-webhook-dingtalk cd prometheus-webhook-dingtalk/contrib/k8s/ vim config/config.yaml # 编辑 targets,如下,mention 代表是不是 @ 指定人/所有人targets: webhook_mention_all: url: https://oapi.dingtalk.com/robot/send?access_token=xxx secret: your_dingtalk_secret mention: all: true# 结束编辑kubectl kustomize | kubectl apply -f-
其中钉钉的 webhook 需要群里中创建机器人,安全设置是必须的,这里我们选择的是 secret
部署完成后,我们需要配置 dingtalk 的 reciver 并将指定规则的告警发送到 dingtalk
- 配置了新的 receiver 叫做 dingtalk,application=blog 的会发送到这个 recevier
- digntalk 的 webhook 地址指向 prometheus-webhook-dingtalk ,由于 monitor 相关的都在同一个 namespace 下,直接配置 service
apiVersion monitoring.coreos.com/v1alpha1 kind AlertmanagerConfig metadata name dingtalk namespace monitor-system spec route receiver dingtalk matchersname application value blog receiversname dingtalk webhookConfigsurl http //alertmanager-webhook-dingtalk/dingtalk/webhook_mention_all/send
上述 amcfg 部署后,我们可以在 alertmanager 中查看,prometheus operator 会 merge AlertManagerConfig
由于安全问题,我们没有开放 alertmanager 的 ingress, 我们本地配置好集群的 kubeconfig,使用 kubectl 将远端的 service 转发到本地访问
kubectl -n monitor-system port-forward svc/prometheus-stack-kube-prom-alertmanager 8081:9093
直接访问本地 http://localhost:8081,查看 Status 可以如下图所示,dingtalk 配置已经加入了
此时我们可以进入到 blog
的容器中,执行如下命令,让他的 cpu 直接跑满
for i in`seq 1 $(cat /proc/cpuinfo |grep "physical id" |wc -l)`; do dd if=/dev/zero of=/dev/null & done
可以在 grafana 中查看到该容器的 cpu
此时我们查看 prometheus 的 alert 状态(Inactive-> Pending -> Firing)以及钉钉群的告警,告警通知成功。同时我们还可以看到默认 stack 也配置了对于 pod cpu 的告警
prometheus 还是非常值得去研究一下的,这里只是在搭建完成监控体系的基础上,做了一些小的演示,大家有兴趣可以去深入研究下。
云资源告警
上一个部分我们引入了在 ECS 之上的 机器/应用 层面的告警,这个可以 cover 大部分的场景,但是有些极端场景,导致我们的监控体系崩溃,机器瘫痪,我们需要其他的监控来告知。这就需要我们使用阿里云对于云资源的监控告警,相当于在我们最后的保险。
我们来到云监控,可以看到我们指定区域的 ecs 是否已经部署了监控客户端,我们可以选择手动部署或者批量部署,建议勾选新的 ECS 都自动安装上监控组件。
我们直接批量安装,等待监控组件正常启动
接下来我们验证一下勾选后,新创建的 ECS 是否会自动部署 Agent,我们使用之前的 terraform
新创建一下机器,切换 region 以后,可以看到新起来的 ECS 有部署 Agent 的。
我们解析来看一下告警规则,ECS 支持机器层面非常详细的监控指标,可以满足大部分的场景
并且支持多种联系人通知方式,大家可以参考一下文档。
总结
到这里,基于 ECS 的云端开发/协同办公 集群就已经介绍的差不多了,由于是 ECS 的评测我们手里资源不多情况下使用了 k3s 作为基石,对于上述的内容,均可以在 Kubernetes 上实现。而且机器不仅仅限于 ECS,我个人的集群中大部分都是活动时候买的轻量服务器,看到是不是已经迫不及待了,快动起手来,打造你的远端 “军火库”。