Git flow —— 一种常用的 git 分支模型

git flow

背景

目前团队在 git 实践上还没有一套统一的规范,且存在以下影响开发效率的情况:

  • 不同团队成员对于 git 的使用未达成一致,导致在实际开发协助中需要花费更多额外的时间和精力解决各种意想不到的问题。
  • 不同项目间使用的 git 实践也不一致,导致在切换项目时存在一定的上下文损耗,且更容易出错。

因此,亟需一套大家达成一致的通用的 git 实践以解决上述问题,从而提升效率。

为什么使用 git flow ?

结合团队项目的实际情况以及各主流 git 分支模型的特点,推荐使用最为知名且经过大量项目验证的 git flow 模型。

什么是 git flow ?

Git flow是由 Vincent Driessen 于2010年提出的 git branching workflow,主要特点是基于两个 long-live 分支以及一些用于支援开发周期的 supporting 分支进行分支管理。

long-live 分支

主要包含 masterdevelop 这两个存在于整个研发周期的固定分支。

  • master 分支:对应的是已经在生产环境上的代码。
  • develop 分支:包含下次将要部署到 production 的变更。

当 develop 分支上的代码稳定且可以发版时,其中所有的变更应该通过某种形式(一般是从 develop 分支 checkout out 出对应的 release 分支进行进一步的验证)merge 到 master 分支上,之后再 tag 对应的版本号。

supporting 分支

除了上述 long-live 分支,我们还会使用一些 supporting 分支以支援日常的开发场景。不同于 long-live 分支,supporting 分支都是为了特定的目的而存在,因此这些分支的存活周期是有限的。

我们主要使用的 supporting 分支类型有:

  • Feature 分支:从 develop 分支 checkout 出来,用于新功能的开发。
  • Release 分支:从 develop 分支 checkout 出来,用于部署到 production 前的预发布及验证,另外可以解放 develop 分支以进行后续功能的迭代。
  • Hotfix分支:从 master 分支 checkout 出来,用于快速修复 production 环境发现的 bug。

Feature 分支

Feature branch

  • 只应存在于开发者个人的 repo,不应出现在 upstream repo
  • develop 分支 checkout,必须 merge 到 develop 分支
  • 命名规范:feat/[ticket-no]-[simple-description]

Release 分支

  • 需存在于 upstream repo
  • 仅可基于该分支进行 bug fix
  • develop 分支 checkout(时机为 develop 分支能反映出下次上 production 的状态),必须 merge 到 master 分支(当有在 release 分支进行 bug fix 时,也必须 merge 回 develop 分支)
  • 命名规范:release/[release-no]
  • 版本号:*.0.* -> *.1.01.*.* -> 2.0.0

Hotfix 分支

hotfix branch

  • 只应存在于开发者个人的 repo,不应出现在 upstream repo
  • master 分支 checkout,必须 merge 回 develop 分支与 master 分支(若此时存在 release 分支,则应将 hotfix 分支先 merge 到 release 分支,再将 release 分支 merge 回 develop 分支)
  • 命名规范:hotfix/[issue-no]-[simple-description]
  • 版本号:*.*.1 -> *.*.2

其他约定

  • master, develop, release 分支上的迭代必须通过提 PR 的方式进行,禁止直接修改这些分支。
  • 合并 PR 时使用 create a merge commit 的方式(因为其他方式会导致丢失被合并分支上的 commit 信息,后续若继续使用此原始分支提交 PR,会导致旧 PR 的改动仍然出现在新 PR 中的情况,详情请参考 github 官方文档)。

参考资料

云原生时代,拥抱微服务(三)—— 使用Kubernetes包管理器Helm

前文我们已经将Kubernetes集群搭建好,并且将应用容器化。然而作为开发者,在项目部署中依然觉得直接去管理各种Kubernetes对象是件非常费时且枯燥的事情(要直接去管理各种Kubernetes资源及相关联的标签与选择算符命名空间等)。

很多时候,当某处繁琐且重复时,便是优化开始的地方。 —— 皮皮叨叨

Helm是什么

Helm是对Kubernetes资源部署进一步的抽象,通俗地讲就是个Kubernetes包管理器,相当于Linux中的yum或apt。Helm通过直接部署将Kubernetes资源打包后的Charts,能够简化微服务应用的部署,并且能方便地更新与回滚应用。Helm Chart通过模板的方式部署应用程序。

Helm Chart具有如下目录结构:

YOUR-CHART-NAME/
 |
 |- .helmignore 
 | 
 |- Chart.yaml 
 | 
 |- values.yaml 
 | 
 |- charts/ 
 |
 |- templates/

其中:

  • .helmignore:声明所有在Chart打包时需忽略的文件,类似.gitignore功能。
  • Chart.yaml:存放该Chart的基本信息,如Chart名,版本号等
  • values.yaml:定义所有需要注入模板中的值,该文件中的值可被命令行参数覆盖。
  • templates:此文件夹存放该Chart所需要的Kubernetes资源清单(包括但不限于service、ingress、deployment等),其中的yaml文件使用golang 模板编写,value可以通过Values.yaml中定义的值注入。

小试牛刀(使用Helm安装WordPress)

  1. 安装Helm:

    sudo snap install helm --classic
    # 或
    microk8s.enable helm
  2. 新增环境变量(KUBECONFIG)

    安装完后直接运行helm ls

    运行 helm ls 命令

    会报“Kubernetes集群不可达”的错误。这是因为:helm v3版本不再需要Tiller,而是直接访问ApiServer来与k8s交互,通过环境变量KUBECONFIG来读取存有ApiServre的地址与token的配置文件地址,默认地址为~/.kube/config。而microk8s的相关配置文件路径为/var/snap/microk8s/current/credentials/client.config。解决方法:

    # 在.bashrc文件末尾追加新的KUBECONFIG
    echo export KUBECONFIG=/var/snap/microk8s/current/credentials/client.config >> ~/.bashrc
    # 执行.bashrc
    source .bashrc

    重新运行helm ls:

    运行helm ls命令

    可以看到helm已经能于Kubernetes集群正常交互。

  3. 安装WordPress

    # 添加远程helm chart仓库
    helm repo add bitnami https://charts.bitnami.com/bitnami
    
    # 安装WordPress
    helm install wordpress bitnami/wordpress \
    --namespace=wordpress \
    --set wordpressUsername=your-name,wordpressPassword=your-pwd,mariadb.mariadbRootPassword=db-root-pwd,ingress.enabled=true,ingress.hostname=your.domain.name

    参数含义及更多参数请参考:bitnami/wordpress

    通过运行helm ls -n wordpress可以看到wordpress已经部署(-n 代表namespace)。

    helm ls -n wordpress

    访问:http://your.domain.name

    wordpress网站首页效果

    能看到部署的WordPress已经成功运行,就这么简单,一两条命令的功夫就能拥有个人专属博客站点,Amazing!!!

    kubectl get

    通过kubectl get命令,可以看到Helm在底下帮我们创建好了WordPress站点所需的各种资源,Cool~

    那么Helm究竟使用了什么黑魔法呢?下面我们将通过为容器化后的应用创建Helm Chart来揭开其神秘面纱~

为应用程序定制Helm Chart

上面WordPress安装实质上是通过Helm安装远程仓库经过打包后的Chart实现的,在实际业务场景中我们也可以为应用程序定制Helm Chart。

  1. 创建Helm Chart

    helm create 

    helm create your-helm-chart-name

    可以看到我们已经成功创建了Helm Chart:

    新创建的Helm Chart目录结构

  2. 定制Helm Chart

    上面我们已经生成了一个通用的Chart,对于一些更为复杂的应用我们也可以在此基础上进行定制。基本操作流程就是根据需求修改或新增templates目录下各Kubernetes资源声明模板文件,在其中通过golang template语法注入values.yaml文件中定义的值,具有非常大的灵活性。

    举个栗子🌰,对于一般的API应用我们常常需要部署Job或者CronJob以执行特定任务,而且需要将kubernetes Secretskubernetes ConfigMaps注入到应用程序容器的环境变量中。

    除了工作负载Deployment的模板以外,我们还需要创建Job或者CronJob相关的模板(当然你也可以将三者放到一个模板中,再通过values.yaml中传入条件去生成对应的Kubernetes声明文件)。

    创建需要的工作负载模板

    我们发现上述多个工作负载模板中会有很多重复的内容(如容器的环境变量定义),对于这些可复用的模块我们可以将其在_helpers.tpl文件中新增声明:

    {{/* Add following code to templates/_helpers.tpl */}}
    {{/*
    Create container env to use
    */}}
    {{- define ".helm.container.env" -}}
    env:
     - name: MONGODB_HOST
       valueFrom:
         configMapKeyRef:
           name: your-api
           key: mongodb_host
     - name: MONGODB_DATABASE
       valueFrom:
         configMapKeyRef:
           name: your-api
           key: mongodb_database
     - name: MONGODB_USERNAME
       valueFrom:
         secretKeyRef:
           name: your-api
           key: mongodb_username
     - name: MONGODB_PASSWORD
       valueFrom:
         secretKeyRef:
           name: your-api
           key: mongodb_password
     - name: NODE_ENV
       value: {{ default "production" .Values.nodeEnv }}
    {{- end -}}

    templates/deployment.yaml模板文件:

    {{- if eq .Values.workload.kind "Deployment" }}
    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: {{ include ".helm.fullname" . }}
     labels:
       {{- include ".helm.labels" . | nindent 4 }}
    spec:
     replicas: {{ .Values.replicaCount }}
     strategy:
       type: {{ .Values.updateStrategy.type }}
       rollingUpdate:
         maxSurge: {{ .Values.updateStrategy.rollingUpdate.maxSurge }}
         maxUnavailable: {{ .Values.updateStrategy.rollingUpdate.maxUnavailable }}
     selector:
       matchLabels:
         {{- include ".helm.selectorLabels" . | nindent 6 }}
     template:
       metadata:
         labels:
           {{- include ".helm.selectorLabels" . | nindent 8 }}
       spec:
       {{- with .Values.imagePullSecrets }}
         imagePullSecrets:
           {{- toYaml . | nindent 8 }}
       {{- end }}
         serviceAccountName: {{ include ".helm.serviceAccountName" . }}
         securityContext:
           {{- toYaml .Values.podSecurityContext | nindent 8 }}
         containers:
           - name: {{ .Chart.Name }}
             securityContext:
               {{- toYaml .Values.securityContext | nindent 12 }}
             image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
             imagePullPolicy: {{ .Values.image.pullPolicy }}
             {{- include ".helm.container.env" . | nindent 10 }}
             {{- if .Values.workload.isGeneralDeployment }}
             ports:
               - name: http
                 containerPort: 3000
                 protocol: TCP
             livenessProbe:
               httpGet:
                 path: /api-health-check-endpoint
                 port: http
             readinessProbe:
               httpGet:
                 path: /api-health-check-endpoint
                 port: http
             {{- else }}
             command: {{ .Values.workload.command }}
             args: {{ .Values.workload.args }}
             {{- end }}
             resources:
               {{- toYaml .Values.resources | nindent 12 }}
         {{- with .Values.nodeSelector }}
         nodeSelector:
           {{- toYaml . | nindent 8 }}
         {{- end }}
       {{- with .Values.affinity }}
         affinity:
           {{- toYaml . | nindent 8 }}
       {{- end }}
       {{- with .Values.tolerations }}
         tolerations:
           {{- toYaml . | nindent 8 }}
       {{- end }}
    {{- end }}
    

    templates/cronJob.yaml文件

    {{- if eq .Values.workload.kind "CronJob" }}
    apiVersion: batch/v1beta1
    kind: CronJob
    metadata:
     name: {{ include ".helm.fullname" . }}
     labels:
       {{- include ".helm.labels" . | nindent 4 }}
    spec:
     schedule: {{ .Values.workload.schedule }}
     jobTemplate:
       spec:
         template:
           metadata:
             labels:
               {{- include ".helm.selectorLabels" . | nindent 12 }}
           spec:
           {{- with .Values.imagePullSecrets }}
             imagePullSecrets:
               {{- toYaml . | nindent 12 }}
           {{- end }}
             serviceAccountName: {{ include ".helm.serviceAccountName" . }}
             securityContext:
               {{- toYaml .Values.podSecurityContext | nindent 12 }}
             containers:
               - name: {{ .Chart.Name }}
                 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
                 imagePullPolicy: {{ .Values.image.pullPolicy }}
                 {{- include ".helm.container.env" . | nindent 14 }}
                 command: {{ .Values.workload.command }}
                 args: {{ range .Values.workload.args }}
                   - {{ . }}
                 {{ end }}
             restartPolicy: OnFailure
    {{- end }}
    

    templates/job.yaml文件

    {{- if eq .Values.workload.kind "Job" }}
    apiVersion: batch/v1
    kind: Job
    metadata:
     name: {{ include ".helm.fullname" . }}
     labels:
       {{- include ".helm.labels" . | nindent 4 }}
    spec:
     template:
       metadata:
         labels:
           {{- include ".helm.selectorLabels" . | nindent 8 }}
       spec:
       {{- with .Values.imagePullSecrets }}
         imagePullSecrets:
           {{- toYaml . | nindent 8 }}
       {{- end }}
         serviceAccountName: {{ include ".helm.serviceAccountName" . }}
         securityContext:
           {{- toYaml .Values.podSecurityContext | nindent 8 }}
         containers:
           - name: {{ .Chart.Name }}
             image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
             imagePullPolicy: {{ .Values.image.pullPolicy }}
             {{- include ".helm.container.env" . | nindent 10 }}
             command: {{ .Values.workload.command }}
             args: {{ range .Values.workload.args }}
               - {{ . }}
             {{ end }}
         restartPolicy: Never
    {{- end }}
    

    templates/service.yaml文件:

    {{- if and .Values.workload.isGeneralDeployment (eq .Values.workload.kind "Deployment") -}}
    apiVersion: v1
    kind: Service
    metadata:
     name: {{ include ".helm.fullname" . }}
     labels:
       {{- include ".helm.labels" . | nindent 4 }}
    spec:
     type: {{ .Values.service.type }}
     ports:
       - port: {{ .Values.service.port }}
         targetPort: http
         protocol: TCP
         name: http
     selector:
       {{- include ".helm.selectorLabels" . | nindent 4 }}
    {{- end -}}
    

    在使用支持外部负载均衡器的云提供商的服务时,设置 type 的值为 LoadBalancer, 将为 Service 提供负载均衡器。由于我们使用自建microk8s集群,LoadBalancer并没有效果,所以需要设置ingress以将Service暴露至公网。

    templates/ingress.yaml文件:

    {{- if .Values.ingress.enabled -}}
    {{- $fullName := include ".helm.fullname" . -}}
    {{- $svcPort := .Values.service.port -}}
    {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
    apiVersion: networking.k8s.io/v1beta1
    {{- else -}}
    apiVersion: extensions/v1beta1
    {{- end }}
    kind: Ingress
    metadata:
     name: {{ $fullName }}
     labels:
       {{- include ".helm.labels" . | nindent 4 }}
     {{- with .Values.ingress.annotations }}
     annotations:
       {{- toYaml . | nindent 4 }}
     {{- end }}
    spec:
    {{- if .Values.ingress.tls }}
     tls:
     {{- range .Values.ingress.tls }}
       - hosts:
         {{- range .hosts }}
           - {{ . | quote }}
         {{- end }}
         secretName: {{ .secretName }}
     {{- end }}
    {{- end }}
     rules:
     {{- range .Values.ingress.hosts }}
       - host: {{ .host | quote }}
         http:
           paths:
           {{- range .paths }}
             - path: {{ . }}
               backend:
                 serviceName: {{ $fullName }}
                 servicePort: {{ $svcPort }}
           {{- end }}
     {{- end }}
    {{- end }}
    

    通过values.yaml定义默认值,实际部署时可传参替换。

    # Default values for .helm.
    # This is a YAML-formatted file.
    # Declare variables to be passed into your templates.
    replicaCount: 1
    
    updateStrategy:
     type: RollingUpdate
     rollingUpdate:
       maxSurge: 1
       maxUnavailable: 1
    
    nodeEnv: production
    
    image:
     repository: your.image.repository/path
     tag: latest
     pullPolicy: IfNotPresent
    
    imagePullSecrets: [{ name: your-image-registry }]
    nameOverride: "your-app-name"
    fullnameOverride: "your-app-name"
    
    workload:
     # Support Deployment,Job,CronJob
     kind: Deployment
     isGeneralDeployment: true
     command: ["yarn"]
     args: ["start"]
    
    serviceAccount:
     # Specifies whether a service account should be created
     create: false
     annotations: {}
    
    podSecurityContext: {}
    
    securityContext: {}
    
    service:
     type: ClusterIP
     port: 80
    
    ingress:
     enabled: true
     annotations:
       kubernetes.io/ingress.class: nginx
       kubernetes.io/tls-acme: "true"
     hosts:
       - host: your.domain.name
         paths:
           - '/'
     tls: []
    
    resources: {}
    
    nodeSelector: {}
    
    tolerations: []
    
    affinity: {}
    
  3. 启用Ingress控制器

    为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器Ingress控制器有很多种,我们将使用Kubernetes官方维护的nginx ingress控制器,在microk8s中启用ingress控制器非常简单:

    microk8s.enable ingress  # 将启用nginx ingress控制器
  4. 部署定制的Helm Chart

    helm upgrade -f values.yaml --install --namespace=default --set nodeEnv=staging,workload.kind=Job,workload.args={start-your-job,additional-args},replicaCount=2 your-app-name .

    其中:

    • -f : 默认value文件。
    • --namespace : 部署到某命名空间(对应命名空间需要在Kubernetes集群中存在)
    • --install : 若命名为your-app-name的helm应用不存在则安装,否则更新。
    • --set : 替换默认value的值。
    • your-app-name : helm应用名称(方便管理)
    • . : Helm Chart 所在根目录

    PS: 后续若有需要可以将定制的Helm Chart进行打包,并推送到指定远程仓库。

小结

Helm大大减少了向Kubernetes集群部署应用程序的复杂性,让我们能够快速的部署应用程序,同时也可通过远程的Chart仓库直接安装所需应用。编写定制的Chart也比较直接,需要对Kubernetes资源对象以及golang template有所了解。通过对Helm的应用,能更好地提高开发者的工作效率,以将更多精力聚焦在业务代码的开发上。

云原生时代,拥抱微服务(二)—— 容器化应用

前文我们已经搭建好k8s集群,接下来就可以对部分项目进行容器化改造了。

“万丈高楼平地起,敲好代码是关键。” —— 皮皮叨叨

创建镜像文件Dockerfile

以Node.js应用为例:

FROM node:12-alpine

# install tzdata so that we can set timezone which default is UTC
RUN apk add tzdata
ENV TZ Asia/Shanghai

# set our node environment, either development or production
# defaults to production, can override this to development on build and run
ARG NODE_ENV=production
ENV NODE_ENV $NODE_ENV

# default to port 3000 for node, and 9229 and 9230 (tests) for debug
ARG PORT=3000
ENV PORT $PORT
EXPOSE $PORT 9229 9230

# install dependencies first, in a different location for easier app bind mounting for local development
# due to default /opt permissions we have to create the dir with root and change perms
RUN mkdir /your-app && chown node:node /your-app
RUN mkdir /var/log/your-app && chown node:node /var/log/your-app
WORKDIR /your-app
# the official node image provides an unprivileged user as a security best practice
# but we have to manually enable it. We put it here so yarn installs dependencies as the same
# user who runs the app.
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
USER node
COPY --chown=node:node . .
RUN yarn install --production=true
ENV PATH /your-app/node_modules/.bin:$PATH

CMD [ "yarn", "start" ]

PS: .dockerignore文件可通过gitignore.io生成。

应用配置信息及密钥管理

普通配置信息

ConfigMaps是一种Kubernetes资源,允许你为容器保存配置信息,这些配置后续可以通过挂载为容器的环境变量或者文件进行访问。

# your-config.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: your-api
data:
  mongodb_host: your.mongodb.host
  mongodb_database: your-database-name

通过在k8s集群中使用kubectl apply命令创建上述命名为your-apiConfigMap资源(如果该资源已经存在,则进行更新动作)。

kubectl apply -f path-to/your-config

密钥等敏感信息

不像ConfigMapsSecrets是一种用于存放敏感信息的Kubernetes资源,一般可用于存放各类凭证、ssh密钥,TLS密钥及证书,或者其他在应用中需要使用的敏感数据。Secrets同样可以挂载为容器的环境变量或者文件。

Secrets被保存为base64编码的字符串,我们需要在Secrets声明文件中将对应敏感信息(如数据库用户名与密码)转换为base64格式。可使用如下命令:

echo -n "db-user-name" | base64
# output: ZGItdXNlci1uYW1l

echo -n "db-pwd" | base64
# output: ZGItcHdk

注意,上述命令输出后的base64编码字符串就是我们需要保持到Kubernetes中的Secrets信息。

# your-secrets.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: your-api
data:
  mongodb_username: ZGItdXNlci1uYW1l
  mongodb_password: ZGItcHdk

使用kubectl apply命令创建对应Secrets资源:

kubectl apply -f path-to/your-secrets.yaml

【温馨提示】base64编码的字符串并不是被加密的,需要将上述Secrets声明文件保存到足够安全的地方,并且防止泄露!!!建议在Kubernetes集群中统一进行管理。

为应用程序创建Deployment工作负载

工作负载(Workloads)资源可以理解为在Kubernetes中的应用程序,主要包括:DeploymentStatefulSetDaemonSetJobCronJob等类型。

Deployment主要用于扩展与更新无状态容器Pod,适用于我们无状态的应用程序。Deployment会自动创建副本集(ReplicaSet)资源,以用于替换失败的Pods,弹性扩展或减少Pods

# your-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: your-api
  labels:
    app: your-api-label
spec:
  replicas: 3
  selector:
    matchLabels:
      app: your-api-label
  template:
    metadata:
      labels:
        app: your-api-label
    spec:
      containers:
        - name: your-api
          image: your-api-image:1.0.0
          env:
            - name: MONGODB_HOST
              valueFrom:
                configMapKeyRef:
                  name: your-api
                  key: mongodb_host
            - name: MONGODB_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: your-api
                  key: mongodb_database
            - name: MONGODB_USER
              valueFrom:
                secretKeyRef:
                  name: your-api
                  key: mongodb_username
            - name: MONGODB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: your-api
                  key: mongodb_password

使用kubectl apply命令创建对应Deployment资源:

kubectl apply -f path-to/your-depoyment.yaml

使用Service公开Deployment工作负载

我们还需要通过创建Service资源以更好地访问不同Pod中的应用程序,并且能做到负载均衡。这主要是由于Pod是短暂的,一旦由于某种原因被销毁,其对应的IP地址也会被吊销,然后新生的Pod将获得一个不同的IP地址。

# your-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: your-api
spec:
  type: ClusterIP
  selector:
    app: your-api-label
  ports:
    - protocol: TCP
      port: 80
      targetPort: http

使用kubectl apply命令创建对应Service资源:

kubectl apply -f path-to/your-service.yaml

总结

我们首先通过创建Dockerfile以用于生成应用程序的容器镜像,然后通过使用ConfigMapsSecrets资源管理应用程序的配置及敏感信息,最后再运用DeploymentService资源部署及公开应用程序。

微服务高楼的基石貌似已经搭好了,胜利的曙光就在眼前。但是机智且追求效率的你一定发现了:一个应用程序这样做还好,可如果有多个应用程序也需要容器化,都分别去声明各种Kubernetes资源岂不是很繁琐?你这个ClusterIP类型的Service似乎还无法从外网访问?

云原生时代,拥抱微服务(一) —— 搭建k8s集群

背景

现有系统是以传统的单片形式进行架构,通过采用Nginx对后端项目及前端进行反向代理。基础设施使用的是阿里云ECS服务器,直接在ECS中运行项目。在项目初期这种方式可以较快的搭建起应用,但随着项目不断地迭代,整个后端系统变得越来越臃肿,且难以进行弹性扩展和维护。为了让日后的开发及运维更为平顺,微服务化势在必行。

时间就是生命,提升效率就是延长生命。 —— 皮皮叨叨

既然有了这个想法,下面就要一步步落实了。微服务化第一步就是要将项目进行容器化,而为了更好地管理容器就需要一套能“用于自动部署、扩展和管理容器化应用程序的平台”,我们将采用业界普遍使用的Kubernetes作为容器管理平台。

云平台K8S or 自行搭建?

我们最终选择自行搭建K8S平台,主要基于下述原因:

  • 已经购买了包年的ECS套餐
  • 基于费用成本考虑,阿里云容器服务Kubernetes版计费更为复杂(除了集群管理费,还包括云服务器、NAT网关、负载均衡等相关费用),总体而言费用相对更高。
  • 需要平稳过渡到新的部署架构,先从后端项目入手,成熟后再将前端等其他项目迁移过去。
  • 如果后续有需要的话,自建K8S也可以很快的迁移到使用云平台K8S方案(通过配置文件对Kubernetes对象进行声明式管理)

我们将使用轻量的、产品级且易于运维的microk8s搭建K8S集群。

搭建microk8s集群(基于Ubuntu 18.04)

  1. 安装snappy包管理器(若已安装,请忽略)
sudo apt update
sudo apt install snapd
  1. 安装microk8s(以1.17版本为例)
sudo snap install microk8s --classic --channel=1.17/stable

## 配置防火墙以允许pod间通信
sudo ufw allow in on cni0 && sudo ufw allow out on cni0

## 创建kubectl别名以方便后续使用
echo "alias kubectl='microk8s.kubectl'" >> ~/.bashrc
source ~/.bashrc
## 或者
sudo snap alias microk8s.kubectl kubectl
  1. 开启CoreDNS以支持k8s地址解析服务(推荐启用)
microk8s.enable dns 

## 查看microk8s状态
microk8s.status

可以看到microk8s已成功运行且dns插件已启用

## 检查coreDNS运行情况
kubectl get pods --all-namespaces

相关pod处于containerCreateing状态

发现相关pod一直处于ContainerCreating状态!!!

## 继续查找原因
kubectl describe pod/coredns-9b8997588-dp9cx -n kube-system

发现由于某种原因 *无法获取* 指定镜像 **k8s.gcr.io/pause:3.1**

发现由于某种原因 无法获取 指定镜像 k8s.gcr.io/pause:3.1解决方法如下:

## 下载docker hub 上的相关镜像
sudo docker pull mirrorgooglecontainers/pause:3.1 
## 将相关镜像tag为 k8s.gcr.io/pause:3.1
sudo docker tag mirrorgooglecontainers/pause:3.1 k8s.gcr.io/pause:3.1
## 保存镜像为本地镜像文件
sudo docker save k8s.gcr.io/pause > pause.tar
## ctr导入本地镜像
sudo microk8s.ctr image import pause.tar
  1. 增加microk8s节点
## 主节点运行命令
microk8s add-node

上述命令打印出microk8s join指令,用于在其他需要加入集群的节点执行。PS:需要确保节点间网络互通(并在安全组及主机防火墙开放),否则加入不成功!!!

## 查看k8s集群节点信息
kubectl get nodes


大功告成!!!接下来可以愉快地对项目进行容器化管理了^-^