作者:牛玉富,某知名互联网公司专家工程师。喜欢开源 / 热衷分享,对 K8s 及 golang 网关有较深入研究。
本文将解读如何利用云原生解决私有化交付中的问题,进而打造一个 PaaS 平台,提升业务平台的复用性。在进入正题之前,有必要先明确两个关键词:

如上图:私有云会有明确的安全性要求
基于以上要求面临的挑战大概有几点:

我们的原则是 拥抱云原生和复用已有能力,近可能使用业界已存在且成熟技术方案。
我们采用 KubeSphere+K8S 作为服务编排,处于安全性及简洁性考虑对 Syncd 进行二次开发完整 DevOps 能力,监控系统上采用 Nightingale+Prometheus 方案。
如上图架构图
下面我们针对这三部分做下介绍。
KubeSphere 的愿景是打造一个以 K8s 为内核的云原生分布式操作系统,它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用(plug-and-play)的集成,支持云原生应用在多云与多集群的统一分发和运维管理,同时它还拥有活跃的社区。
KubeSphere 选型理由有以下几点:
创建制品清单 :
apiVersion: kubekey.kubesphere.io/v1alpha2
kind: Manifest
metadata:
name: sample
spec:
arches:
- amd64
...
- type: kubernetes
version: v1.21.5
components:
helm:
version: v3.6.3
cni:
version: v0.9.1
etcd:
version: v3.4.13
containerRuntimes:
- type: docker
version: 20.10.8
crictl:
version: v1.22.0
harbor:
version: v2.4.1
docker-compose:
version: v2.2.2
images:
- dockerhub.kubekey.local/kubesphere/kube-apiserver:v1.22.1
...
然后我们就可以通过命令进行导出了。
$ ./kk artifact export -m manifest-sample.yaml -o kubesphere.tar.gz
创建部署清单:
apiVersion: kubekey.kubesphere.io/v1alpha2
kind: Cluster
metadata:
name: sample
spec:
hosts:
- {name: kubesphere01.ys, address: 10.89.3.12, internalAddress: 10.89.3.12, user: kubesphere, password: "Kubesphere123"}
- {name: kubesphere02.ys, address: 10.74.3.25, internalAddress: 10.74.3.25, user: kubesphere, password: "Kubesphere123"}
- {name: kubesphere03.ys, address: 10.86.3.66, internalAddress: 10.86.3.66, user: kubesphere, password: "Kubesphere123"}
- {name: kubesphere04.ys, address: 10.86.3.67, internalAddress: 10.86.3.67, user: kubesphere, password: "Kubesphere123"}
- {name: kubesphere05.ys, address: 10.86.3.11, internalAddress: 10.86.3.11, user: kubesphere, password: "Kubesphere123"}
roleGroups:
etcd:
- kubesphere01.py
- kubesphere02.py
- kubesphere03.py
control-plane:
- kubesphere01.py
- kubesphere02.py
- kubesphere03.py
worker:
- kubesphere05.py
registry:
- kubesphere04.py
controlPlaneEndpoint:
internalLoadbalancer: haproxy
domain: lb.kubesphere.local
address: ""
port: 6443
kubernetes:
version: v1.21.5
clusterName: cluster.local
network:
plugin: calico
kubePodsCIDR: 10.233.64.0/18
kubeServiceCIDR: 10.233.0.0/18
multusCNI:
enabled: false
registry:
type: harbor
auths:
"dockerhub.kubekey.local":
username: admin
password: Kubesphere123
...
执行安装部署:
$ ./kk create cluster -f config-sample.yaml -a kubesphere.tar.gz --with-packages --with-kubesphere --skip-push-images
原来大量复杂的 K8s 部署、高可用方案、Harbor 私有化镜像仓库等,均可以完成自动化安装,极大的简化了私有化交付场景下 K8s 组件部署难度。



私有化场景构建高可用服务实例部署,保障单实例挂掉不影响整体使用,我们要保证以下几点。
1、由于服务都需要有固定的网络标识和存储,所以我们需要创建 “有状态副本集部署”。
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: project
name: ${env_project_name}
labels:
app: ${env_project_name}
spec:
serviceName: ${env_project_name}
replicas: 1
selector:
matchLabels:
app: ${env_project_name}
template:
metadata:
labels:
app: ${env_project_name}
spec:
containers:
- name: ${env_project_name}
image: ${env_image_path}
imagePullPolicy: IfNotPresent
2、有状态副本集使用 host 反亲和性保证服务分散到不同 host 中。
....
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- ${env_project_name}
topologyKey: kubernetes.io/hostname
....
3、服务与服务之间互相调用均使用 K8s 底层的 DNS 进行配置。

4、集群内部依赖外部资源时需要设置为 Service,然后在内部提供服务。
kind: Endpoints
apiVersion: v1
metadata:
name: redis-cluster
namespace: project
subsets:
- addresses:
- ip: 10.86.67.11
ports:
- port: 6379
---
kind: Service
apiVersion: v1
metadata:
name: redis-cluster
namespace: project
spec:
ports:
- protocol: TCP
port: 6379
targetPort: 6379
5、借助 nip.io 域名实现服务动态域名解析调试。
nip.io 可以自动根据请求的域名中设置 IP 信息,完成响应的 IP 信息映射。
$ nslookup abc-service.project.10.86.67.11.nip.io
Server: 169.254.25.10
Address: 169.254.25.10:53
Non-authoritative answer:
Name: abc-service.project.10.86.67.11.nip.io
Address: 10.86.67.11
因此我们可以在构建 Ingress 时直接使用该域名:
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: gatekeeper
namespace: project
spec:
rules:
- host: gatekeeper.project.10.86.67.11.nip.io
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: gatekeeper
port:
number: 8000
6、挂载目录到宿主机,有时候需要容器直接关联宿主机目录具体操作如下。
...
spec:
spec:
...
volumeMounts:
- name: vol-data
mountPath: /home/user/data1
volumes:
- name: vol-data
hostPath:
path: /data0
7、有状态部署工作负载,主要涉及 StatefulSet、Service、volumeClaimTemplates、Ingress,示例如下:
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: project
name: gatekeeper
labels:
app: gatekeeper
spec:
serviceName: gatekeeper
replicas: 1
selector:
matchLabels:
app: gatekeeper
template:
metadata:
labels:
app: gatekeeper
spec:
containers:
- name: gatekeeper
image: dockerhub.kubekey.local/project/gatekeeper:v362
imagePullPolicy: IfNotPresent
ports:
- name: http-8000
containerPort: 8000
protocol: TCP
- name: http-8080
containerPort: 8080
protocol: TCP
resources:
limits:
cpu: '2'
memory: 4Gi
volumeMounts:
- name: vol-data
mountPath: /home/user/data1
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- gatekeeper
topologyKey: kubernetes.io/hostname
volumeClaimTemplates:
- metadata:
name: vol-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: gatekeeper
namespace: project
labels:
app: gatekeeper
spec:
ports:
- name: "http-8000"
protocol: TCP
port: 8000
targetPort: 8000
- name: "http-8080"
protocol: TCP
port: 8080
targetPort: 8080
selector:
app: gatekeeper
type: NodePort
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: gatekeeper
namespace: project
spec:
rules:
- host: gatekeeper.project.10.86.67.11.nip.io
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: gatekeeper
port:
number: 8000
- host: gatekeeper.project.10.86.68.66.nip.io
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: gatekeeper
port:
number: 8080
DevOps 选型有很多,这里我们没有采用 Jenkins、GitRunner 等等,而是使用了我们团队内部比较熟悉的 Syncd 进行二次开发。原因有两点:
Syncd 核心思路:
1、基于项目创建目录
#创建目录
cd /Users/niuyufu/goproject/abc-service
mkdir -p devops
cd devops
2、导入 Dockerfile,大家可基于业务自行创建。
3、创建 tool.sh 文件
cat >> tool.sh << EOF
#!/bin/sh
###########配置区域##############
#模块名称,可变更
module=abc-service
#项目名称
project=project1
#容器名称
container_name=${project}"_"${module}
#镜像名称
image_name=${project}"/"${module}
#服务端口映射:宿主机端口:容器端口,多个逗号间隔
port_mapping=8032:8032
#镜像hub地址
image_hub=dockerhub.kubekey.local
#镜像tag
image_tag=latest
###########配置区域##############
#构建工具
action=$1
case $action in
"docker_push")
image_path=${image_hub}/${image_name}:${image_tag}
docker tag ${image_name}:${image_tag} ${image_path}
docker push ${image_path}
echo "镜像推送完毕,image_path: "${image_path}
;;
"docker_login")
container_id=$(docker ps -a | grep ${container_name} | awk '{print $1}')
docker exec -it ${container_id} /bin/sh
;;
"docker_stop")
docker ps -a | grep ${container_name} | awk '{print $1}' | xargs docker stop
container_id=`docker ps -a | grep ${container_name} | awk '{print $1}' | xargs docker rm`
if [ "$container_id" != "" ];then
echo "容器已关闭,container_id: "${container_id}
fi
if [ "$images_id" != "" ];then
docker rmi ${images_id}
fi
;;
"docker_run")
docker ps -a | grep ${container_name} | awk '{print $1}' | xargs docker stop
docker ps -a | grep ${container_name} | awk '{print $1}' | xargs docker rm
port_mapping_array=(${port_mapping//,/ })
# shellcheck disable=SC2068
for var in ${port_mapping_array[@]}; do
port_mapping_str=${mapping_str}" -p "${var}
done
container_id=$(docker run -d ${port_mapping_str} --name=${container_name} ${image_name})
echo "容器已启动,container_id: "${container_id}
;;
"docker_build")
if [ ! -d "../output" ]; then
echo "../output 文件夹不存在,请先执行 ../build.sh"
exit 1
fi
cp -rf ../output ./
docker build -f Dockerfile -t ${image_name} .
rm -rf ./output
echo "镜像编译成功,images_name: "${image_name}
;;
*)
echo "可运行命令:
docker_build 镜像编译,依赖../output 文件夹
docker_run 容器启动,依赖 docker_build
docker_login 容器登陆,依赖 docker_run
docker_push 镜像推送,依赖 docker_build"
exit 1
;;
esac
EOF
4、执行项目打包,请确保产出物在 ./output 中
$cd ~/goproject/abc-service/
$sh build.sh
abc-service build ok
make output ok
build done
5、利用 tool.sh 工具进行服务调试
tools.sh 执行顺序一般是这样的:./output 产出物→docker_build→docker_run→docker_login→docker_push
$cd devops
$chmod +x tool.sh
#查看可运行命令
$sh tool.sh
可运行命令:
docker_build 镜像编译,依赖../output 文件夹
docker_run 容器启动,依赖 docker_build
docker_login 容器登陆,依赖 docker_run
docker_push 镜像推送,依赖 docker_build
#docker_build举例:
$sh tool.sh docker_build
[+] Building 1.9s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B
... 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:0a1fba79684a1a74fa200b71efb1669116c8dc388053143775aa7514391cdabf 0.0s
=> => naming to docker.io/project/abc-service 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
镜像编译成功,images_name: project/abc-service
#docker_run举例:
$ sh tool.sh docker_run
6720454ce9b6
6720454ce9b6
容器已启动,container_id: e5d7c87fa4de9c091e184d98e98f0a21fd9265c73953af06025282fcef6968a5
#可以使用 docker_login 登陆容器进行代码调试:
$ sh tool.sh docker_login
sh-4.2# sudo -i
root@e5d7c87fa4de:~$
#docker_push举例:
$sh tool.sh docker_push 130 ↵
The push refers to repository [dockerhub.kubekey.local/citybrain/gatekeeper]
4f3c543c4f39: Pushed
54c83eb651e3: Pushed
e4df065798ff: Pushed
26f8c87cc369: Pushed
1fcdf9b8f632: Pushed
c02b40d00d6f: Pushed
8d07545b8ecc: Pushed
ccccb24a63f4: Pushed
30fe9c138e8b: Pushed
6ceb20e477f1: Pushed
76fbea184065: Pushed
471cc0093e14: Pushed
616b2700922d: Pushed
c4af1604d3f2: Pushed
latest: digest: sha256:775e7fbabffd5c8a4f6a7c256ab984519ba2f90b1e7ba924a12b704fc07ea7eb size: 3251
镜像推送完毕,image_path: dockerhub.kubekey.local/citybrain/gatekeeper:latest
#最后登陆Harbor测试镜像是否上传
https://dockerhub.kubekey.local/harbor/projects/52/repositories/gatekeeper
1、项目配置
新增项目

设置 tool.sh 中生成的镜像地址。

设置构建脚本。

参照有状态工作负载填写构建脚本。

2、创建上线单

3、构建部署包执行部署

4、切换到 KubeSphere 查看部署效果。

至此已完成 DevOps 与 KubeSphere 的功能打通。


$ git clone https://github.com/flashcatcloud/n9e-helm.git
$ helm install nightingale ./n9e-helm -n n9e --create-namespace





私有化交付下因业务场景不同,对云原生的应用选型也不相同。本文仅对我们自身业务场景做了介绍,如有问题欢迎指正,另外其他场景下的云原生应用也随时欢迎大家来和我交流探讨。
本文由博客一文多发平台 OpenWrite 发布!
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
假设您编写了一个类Sup,我决定将其扩展为SubSup。我不仅需要了解你发布的接口(interface),还需要了解你的私有(private)字段。见证这次失败:classSupdefinitialize@privateField="fromsup"enddefgetXreturn@privateFieldendendclassSub问题是,解决这个问题的正确方法是什么?看起来子类应该能够使用它想要的任何字段而不会弄乱父类(superclass)。编辑:equivalentexampleinJava返回"fromSup",这也是它应该产生的答案。 最佳答案
我正在尝试获得良好的Ruby编码风格。为防止意外调用具有相同名称的局部变量,我总是在适当的地方使用self.。但是现在我偶然发现了这个:classMyClass上面的代码导致错误privatemethodsanitize_namecalled但是当删除self.并仅使用sanitize_name时,它会起作用。这是为什么? 最佳答案 发生这种情况是因为无法使用显式接收器调用私有(private)方法,并且说self.sanitize_name是显式指定应该接收sanitize_name的对象(self),而不是依赖于隐式接收器(也是