K8S service部署笔记
从零开始部署一个完整的服务到K8S集群。
前面开发了一个工具,一直在本地跑。现在需要发布到K8S里面,方便维护和远程访问,因为别人需要使用这个工具.
部署一个新的K8S需要覆盖下面的内容:
- 创建Dockerfile. 这个用于build image.
- 创建Jenkins文件,用于Jenkins Job使用. build image和上传image.
- 创建Service chart
- 创建Jenkins Job用来打包docker image和service chart. 需要用到前面3步创建的内容。service chart可以用Jenkins打包,也可以不打包,手动改参数, 然后让ArgoCD自己去读git repo的目录解析chart.
- 部署servie chart到开发环境
- 数据持久化
- 数据库初始化
- 添加sidekiq pod
- 域名绑定
初次构建K8S的时候尽量把一些安全方面的问题,合规方面的问题先不要考虑。尽量用公开的image,用简单常用的脚步和步骤,简化image的build, 简化chart的build.
用到的工具:
- Helm, 创建模版chart.
- Docker, build image
- Jenkins, 编译docker image.
- ArgoCD, 部署集群. 在ArgoCD上新建APP, 指定chart路径,让ArgoCD构建App.
- Rancher, 远程运维service.
6.
创建service chart
拷贝了一份service chart(adserver
下面拷贝的)到mobile-mobile-dev-config
的dce
分支下面, 里面包含如下内容:
- templates, 这下面的内容会被用来生成service chart.
- _helpers.tpl,放置可以通过chart复用的模板辅助对象
- deployment.yaml, 用来定义和部署pod, label, app name,ports, readiness/liveness等. service.yaml里面定义的服务会把pod暴露给集群。
- hpa.yaml, HorizontalPodAutoscaler, 水平扩张,高可用的东西. 需要多少replicas(minReplicas, maxReplicas), memory, cpu等.
- ingress.yaml, 域名配置
- servicePort的值长度要在15个字符以内
crashreporthttp
- servicePort的值长度要在15个字符以内
- NOTES.txt, helm install时展示给用户的.
- service.yaml,为你的工作负载创建一个 service终端基本清单。用来暴露deployment.yaml里面创建的内容。
- Chart.yaml,
- values.yaml
Ingress 配置
- 在
deployment.yaml
里面暴露pod端口3000
--- apiVersion: apps/v1 kind: Deployment metadata: name: crashreport-app # Deployment name labels: app: crashreport-app # 标签(Labels) 是附加到 Kubernetes 对象(比如 Pods)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。 spec: strategy: type: RollingUpdate replicas: 1 revisionHistoryLimit: 0 selector: matchLabels: app: crashreport-app # 匹配label template: metadata: labels: app: crashreport-app spec: serviceAccountName: imagePullSecrets: containers: - name: crashreport-main image: "/:" # docker image url imagePullPolicy: Always ports: - name: crashreporthttp containerPort: # 暴露给NAT的端口.
-
在
service.yaml
里面把targetPort
绑定到app(pod)
上去, 请求来的时候会随机从Deployment
里选一个pod
做转发.apiVersion: v1 kind: Service metadata: name: vip-crashreport labels: app: crashreport-app spec: type: ClusterIP ports: - port: targetPort: crashreporthttp name: crashreporthttp selector: app: crashreport-app
-
ingress.yaml
里面指定nginx, 然后指定host(domain name), path(/), 绑定serviceName和servicePort(第2步里面定义的).apiVersion: extensions/v1beta1 kind: Ingress metadata: name: crashreport-nginx annotations: kubernetes.io/ingress.class: nginx.ingress.kubernetes.io/proxy-body-size: 100m nginx.ingress.kubernetes.io/enable-cors: "true" nginx.ingress.kubernetes.io/configuration-snippet: | more_set_headers "Server: K8SDemo"; location /api/v1/notification-deactivation { return 404; } spec: tls: - hosts: - secretName: rules: - host: http: paths: - path: "/" backend: serviceName: vip-crashreport servicePort: crashreporthttp
4.
K8S网络结构
- Pod内的容器之间可以通过容器名来访问.
- 每个 Service 都会自动分配一个 cluster IP(仅在集群内部可访问的虚拟地址)和 DNS 名,其他容器可以通过该地址或 DNS 来访问服务,而不需要了解后端容器的运行。Kubernetes概念
- 对外暴露service可以通过ingress来配置。
K8S数据库
数据库是有状态的,数据需要持久化,和app集群的搭建有点不一样。
搭建数据库需要引入下面的概念:
- StatefulSet, 替代Deployment的概念. Deployment用于部署无状态服务,StatefulSet用来部署有状态服务。这里会指定运行哪个db image,传哪些数据库参数。限制内存,cpu。
- StatefulSet 是用于管理有状态应用的工作负载对象,与 ReplicaSet 和 Deployment 这两个对象不同,StatefulSet 不仅能管理 Pod 的对象,还它能够保证这些 Pod 的顺序性和唯一性。
- StatefulSet里面必须要要有serviceName.这个会被用作域名的一部分,必须符合互联网域名命名规范。
- 与 Deployment 一样,StatefulSet 也使用规格中声明的 template 模板来创建 Pod 资源,但是这些 Pod 相互之间是不能替换的;除此之外 StatefulSet 会为每个 Pod 设置一个单独的持久标识符,这些用于标识序列的标识符在发生调度时也不会丢失。
- Service, 暴露StatefulSet用的. 可以通过statufulset-name.service-name这种域名的形式访问, 集群起来以后会默认有一个crashreport-db0.crashreport-db域名供使用, 该host-name crashreport-db0可以从endpoint的subsets里找到, crashreport-db是service name. 更加有区分性的例子是dev-config,dce分支下
cache/cache-sts.yaml
里面的内容. - StorageClass, 定义PV用的, PV是静态的磁盘, StorageClass可以动态扩展PV。PV的动态创建依赖于StorageClass对象。我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成。可以选postgresql或者mysql.由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。StorageClass要每个数据库一个独立的name, 避免串掉。
- Secret. 保存一些私密信息,密码。
- PersistentVolumeClaim. 把PersistentVolume挂StatefulSet上,挂载到某个路径上,给数据库保存数据用. storageClassName指向StorageClass里面指定的name. PVC和PV的绑定是通过StorageClassName进行的。
数据库初始化
正规的方法是起一个job.登录到pod里面去初始化,不折腾了。
Sidekiq部署
因为sidekiq和主app代码完全一样,所以直接在主app的deployment里面复制了一份deployment,改了一下环境变量,然后在app entrypoint.sh
里面通过环境变量决定是起app还是起sidekiq. sidekiq通过redis通信,所以不需要把sidekiq的deployment通过service暴露给app, 省了一个service.
注意, 除了复制deployment外,还可以通过_helpers.tpl里面的模板来生成多份deployment, 不过那个模板语法需要学习,OPS不是我的最终目标,就不去折腾了。
下面是更新后的deployment文件:
--- # app
apiVersion: apps/v1
kind: Deployment
metadata:
name: crashreport-app
labels:
app: crashreport-app
spec:
strategy:
type: RollingUpdate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: crashreport-app
template:
metadata:
labels:
app: crashreport-app
spec:
serviceAccountName:
imagePullSecrets:
containers:
- name: crashreport-main
image: "/:"
imagePullPolicy: Always
ports:
- name: crashreporthttp
containerPort:
env:
- name: RAILS_ENV
value: production
- name: SERVICE_TYPE
value: crashreport
--- #sidekiq statefulset
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: crashreportsidekiq
labels:
app: crashreportkiq
spec:
updateStrategy: # 这地方和Deployment不同,Deployment里面就叫strategy
type: RollingUpdate
replicas: 1
revisionHistoryLimit: 0
serviceName: crashreport-sidekiq # 必须要,作为namespace/domain的一部分, 要符合域名命名规范。
selector:
matchLabels:
app: crashreportkiq
template:
metadata:
labels:
app: crashreportkiq
spec:
serviceAccountName:
imagePullSecrets:
containers:
- name: crashreport-main
image: "/:"
imagePullPolicy: Always
ports:
- name: crashreporthttp
containerPort:
env:
- name: RAILS_ENV
value: production
- name: SERVICE_TYPE
value: sidekiq
volumeMounts: # PVC绑定
- name: crashlog-disk
mountPath: /app/crashlogs
volumeClaimTemplates: # PVC template
- metadata:
name: crashlog-disk
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1000Gi
#
问题记录
- push docker image的时候每次tag应该不一样,要不然会抛错:
manifest invalid: manifest invalid
- K8s - Kubernetes重要概念介绍(Cluster、Master、Node、Pod、Controller、Service、Namespace)