├── README.md ├── SUMMARY.md └── helm-chart-creation-tutorial ├── src ├── Dockerfile └── main.go ├── my-hello-world ├── .helmignore ├── templates │ ├── service.yaml │ ├── ingress.yaml │ ├── NOTES.txt │ ├── _helpers.tpl │ └── deployment.yaml ├── Chart.yaml └── values.yaml └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # 开放云原生应用中心 2 | 3 | > 本站点提供云原生应用相关文档 4 | 5 | ## 目录 6 | 7 | ### [Helm Chart 创作指南](helm-chart-creation-tutorial/) 8 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [开放云原生应用中心](README.md) 4 | * [Helm Chart 创作指南](helm-chart-creation-tutorial/README.md) 5 | 6 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13-rc-alpine3.10 as builder 2 | WORKDIR /app 3 | COPY main.go . 4 | RUN go build -o hello-world main.go 5 | 6 | FROM alpine:3.10 7 | WORKDIR /app 8 | ARG PORT=80 9 | COPY --from=builder /app/hello-world /app/hello-world 10 | ENTRYPOINT ./hello-world 11 | EXPOSE 80 12 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | port := os.Getenv("PORT") 11 | if port == "" { 12 | port = "80" 13 | } 14 | username := os.Getenv("USERNAME") 15 | if username == "" { 16 | username = "world" 17 | } 18 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 19 | fmt.Fprintf(w, "Hello %s\n", username) 20 | }) 21 | http.ListenAndServe(":"+port, nil) 22 | } 23 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "my-hello-world.fullname" . }} 5 | labels: 6 | {{ include "my-hello-world.labels" . | indent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | app.kubernetes.io/name: {{ include "my-hello-world.name" . }} 16 | app.kubernetes.io/instance: {{ .Release.Name }} 17 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: my-hello-world 3 | description: My first hello world helm chart. 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | version: 0.1.1 18 | 19 | # This is the version number of the application being deployed. This version number should be 20 | # incremented each time you make changes to the application. 21 | appVersion: 1.0.0 22 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "my-hello-world.fullname" . -}} 3 | {{- $ingressPath := .Values.ingress.path -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | {{ include "my-hello-world.labels" . | indent 4 }} 10 | {{- with .Values.ingress.annotations }} 11 | annotations: 12 | {{ toYaml . | indent 4 }} 13 | {{- end }} 14 | spec: 15 | {{- if .Values.ingress.tls }} 16 | tls: 17 | {{- range .Values.ingress.tls }} 18 | - hosts: 19 | {{- range .hosts }} 20 | - {{ . }} 21 | {{- end }} 22 | secretName: {{ .secretName }} 23 | {{- end }} 24 | {{- end }} 25 | rules: 26 | {{- range .Values.ingress.hosts }} 27 | - host: {{ . }} 28 | http: 29 | paths: 30 | - path: {{ $ingressPath }} 31 | backend: 32 | serviceName: {{ $fullName }} 33 | servicePort: http 34 | {{- end }} 35 | {{- end }} 36 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for my-hello-world. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: somefive/hello-world 9 | pullPolicy: IfNotPresent 10 | 11 | nameOverride: "" 12 | fullnameOverride: "" 13 | 14 | service: 15 | type: ClusterIP 16 | port: 80 17 | 18 | ingress: 19 | enabled: false 20 | annotations: {} 21 | # kubernetes.io/ingress.class: nginx 22 | # kubernetes.io/tls-acme: "true" 23 | path: / 24 | hosts: 25 | - chart-example.local 26 | tls: [] 27 | # - secretName: chart-example-tls 28 | # hosts: 29 | # - chart-example.local 30 | 31 | resources: {} 32 | # We usually recommend not to specify default resources and to leave this as a conscious 33 | # choice for the user. This also increases chances charts run on environments with little 34 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 35 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 36 | # limits: 37 | # cpu: 100m 38 | # memory: 128Mi 39 | # requests: 40 | # cpu: 100m 41 | # memory: 128Mi 42 | 43 | nodeSelector: {} 44 | 45 | tolerations: [] 46 | 47 | affinity: {} 48 | 49 | Username: AppHub -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.type }} 7 | export NODE_PORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "my-hello-world.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get svc -w {{ template "my-hello-world.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc {{ template "my-hello-world.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.service.port }} 15 | {{- else if contains "ClusterIP" .Values.service.type }} 16 | export POD_NAME=$(kubectl get pods -l "app.kubernetes.io/name={{ template "my-hello-world.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:80 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "my-hello-world.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "my-hello-world.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "my-hello-world.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "my-hello-world.labels" -}} 38 | app.kubernetes.io/name: {{ include "my-hello-world.name" . }} 39 | helm.sh/chart: {{ include "my-hello-world.chart" . }} 40 | app.kubernetes.io/instance: {{ .Release.Name }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} 46 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/my-hello-world/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "my-hello-world.fullname" . }} 5 | labels: 6 | {{ include "my-hello-world.labels" . | indent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: {{ include "my-hello-world.name" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | template: 14 | metadata: 15 | labels: 16 | app.kubernetes.io/name: {{ include "my-hello-world.name" . }} 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | spec: 19 | containers: 20 | - name: {{ .Chart.Name }} 21 | image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" 22 | imagePullPolicy: {{ .Values.image.pullPolicy }} 23 | env: 24 | - name: USERNAME 25 | value: {{ .Values.Username }} 26 | ports: 27 | - name: http 28 | containerPort: 80 29 | protocol: TCP 30 | livenessProbe: 31 | httpGet: 32 | path: / 33 | port: http 34 | readinessProbe: 35 | httpGet: 36 | path: / 37 | port: http 38 | resources: 39 | {{ toYaml .Values.resources | indent 12 }} 40 | {{- with .Values.nodeSelector }} 41 | nodeSelector: 42 | {{ toYaml . | indent 8 }} 43 | {{- end }} 44 | {{- with .Values.affinity }} 45 | affinity: 46 | {{ toYaml . | indent 8 }} 47 | {{- end }} 48 | {{- with .Values.tolerations }} 49 | tolerations: 50 | {{ toYaml . | indent 8 }} 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /helm-chart-creation-tutorial/README.md: -------------------------------------------------------------------------------- 1 | ### Helm Chart 创作指南 2 | 3 | Helm作为当前最流行的Kubernetes应用管理工具之一,整合应用部署所需的K8s资源(包括Deployment,Service等)到Chart中。今天,本文会带领大家学习如何创建一个简单的Chart。 4 | 5 | #### 准备 6 | 7 | 本文基于的是最新的[Helm v3.0.0-alpha.1](https://v3.helm.sh/),Helm v3相较于Helm v2有较大的变动,比如Helm v3没有服务器端的Tiller组件,在Chart的使用上接口也有一些变化。建议没有Helm v3的同学先从Helm的Github仓库中下载最新的[Helm v3 release](https://github.com/helm/helm/releases/tag/v3.0.0-alpha.1)。想要仔细钻研Helm v3各种特性的同学,可以参考Helm v3的完整[官方文档](https://v3.helm.sh/docs/using_helm/)。 8 | 9 | 在下载得到最新的Helm v3后,同学们可以运行`helm init`来初始化Helm。 10 | 11 | #### 开始创作 12 | 13 | 首先,我们需要有一个要部署的应用。这里我们使用一个简单的基于golang的[hello world HTTP服务](https://github.com/cloudnativeapp/handbook/tree/master/helm-chart-creation-tutorial/src/main.go)。该服务通过读取环境变量`USERNAME`获得用户自己定义的名称,然后监听80端口。对于任意HTTP请求,返回`Hello ${USERNAME}。`比如如果设置`USERNAME=world`(默认场景),该服务会返回`Hello world`。 14 | 15 | 准备好要部署的应用镜像后,运行`helm create my-hello-world`,便会得到一个helm自动生成的空chart。这个chart里的名称是`my-hello-world`。 16 | **需要注意的是,Chart里面的my-hello-world名称需要和生成的Chart文件夹名称一致。如果修改my-hello-world,则需要做一致的修改。** 17 | 现在,我们看到Chart的文件夹目录如下 18 | 19 | ```yaml 20 | my-hello-world 21 | ├── charts 22 | ├── Chart.yaml 23 | ├── templates 24 | │   ├── deployment.yaml 25 | │   ├── _helpers.tpl 26 | │   ├── ingress.yaml 27 | │   ├── NOTES.txt 28 | │   └── service.yaml 29 | └── values.yaml 30 | ``` 31 | 32 | 在根目录下的Chart.yaml文件内,声明了当前Chart的名称、版本等基本信息,这些信息会在该Chart被放入仓库后,供用户浏览检索。比如我们可以把Chart的Description改成"My first hello world helm chart"。 33 | 34 | #### 走近Chart 35 | 36 | Helm Chart对于应用的打包,不仅仅是将Deployment和Service以及其它资源整合在一起。我们看到deployment.yaml和service.yaml文件被放在templates/文件夹下,相较于原生的Kubernetes配置,多了很多渲染所用的可注入字段。比如在deployment.yaml的`spec.replicas`中,使用的是`.Values.replicaCount`而不是Kubernetes本身的静态数值。这个用来控制应用在Kubernetes上应该有多少运行副本的字段,在不同的应用部署环境下可以有不同的数值,而这个数值便是由注入的`Values`提供。 37 | 38 | 在根目录下我们看到有一个`values.yaml`文件,这个文件提供了应用在安装时的默认参数。在默认的`Values`中,我们看到`replicaCount: 1`说明该应用在默认部署的状态下只有一个副本。 39 | 40 | 为了使用我们要部署应用的镜像,我们看到deployment.yaml里在`spec.template.spec.containers`里,`image`和`imagePullPolicy`都使用了`Values`中的值。其中`image`字段由`.Values.image.repository`和`.Chart.AppVersion`组成。看到这里,同学们应该就知道我们需要变更的字段了,一个是位于values.yaml内的`image.repository`,另一个是位于Chart.yaml里的`AppVersion`。我们将它们与我们需要部署应用的docker镜像匹配起来。这里我们把values.yaml里的`image.repository`设置成`somefive/hello-world`,把Chart.yaml里的`AppVersion`设置成`1.0.0`即可。 41 | 42 | 类似的,我们可以查看service.yaml内我们要部署的服务,其中的主要配置也在values.yaml中。默认生成的服务将80端口暴露在Kubernetes集群内部。我们暂时不需要对这一部分进行修改。 43 | 44 | 由于部署的hello-world服务会从环境变量中读取`USERNAME`环境变量,我们将这个配置加入deployment.yaml。相关部分如下: 45 | 46 | ```yaml 47 | - name: {{ .Chart.Name }} 48 | image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" 49 | imagePullPolicy: {{ .Values.image.pullPolicy }} 50 | env: 51 | - name: USERNAME 52 | value: {{ .Values.Username }} 53 | ``` 54 | 55 | 现在我们的deployment.yaml模版会从values.yaml中加载`Username`字段,因此相应的,我们也在values.yaml中添加`Username: AppHub`。 56 | 57 | #### 打包使用 58 | 59 | 完成上述配置后,我们可以使用`helm lint --strict my-hello-world`进行格式检查。如果显示 60 | 61 | ```bash 62 | 1 chart(s) linted, 0 chart(s) failed 63 | [INFO] Chart.yaml: icon is recommended 64 | ``` 65 | 66 | 那么我们就已经离成功只差一步之遥了。 67 | 68 | 接下来,我们运行`helm package my-hello-world`指令对我们的Chart文件夹进行打包。现在我们就得到了`my-hello-world-0.1.0.tgz`的Chart包。到这一步我们的Chart便已经完成了。 69 | 70 | 之后,运行`helm instal my-hello-world-chart-test my-hello-world-0.1.0.tgz`来将本地的chart安装到my-hello-world-chart-test的Release中。运行`kubectl get pods`我们可以看到要部署的pod已经处于运行状态 71 | 72 | ```bash 73 | NAME READY STATUS RESTARTS AGE 74 | my-hello-world-chart-test-65d6c7b4b6-ptk4x 1/1 Running 0 4m3s 75 | ``` 76 | 77 | 运行`kubectl port-forward my-hello-world-chart-test-65d6c7b4b6-ptk4x 8080:80`后,就可以直接在本地运行`curl localhost:8080`看到`Hello AppHub`了! 78 | 79 | #### 进阶使用 80 | 81 | 上述提到values.yaml只是Helm install参数的默认设置,我们可以在安装Chart的过程中使用自己的参数覆盖。比如我们可以运行`helm install my-hello-world-chart-test2 my-hello-world-0.1.0.tgz --set Username="Cloud Native"`来安装一个新Chart。同样运行`kubectl port-forward`进行端口映射,这时可以得到`Hello Cloud Native`。 82 | 83 | 我们注意到在安装Chart指令运行后,屏幕的输出会出现 84 | 85 | ```bash 86 | NOTES: 87 | 1. Get the application URL by running these commands: 88 | export POD_NAME=$(kubectl get pods -l "app=my-hello-world,release=my-hello-world-chart-test2" -o jsonpath="{.items[0].metadata.name}") 89 | echo "Visit http://127.0.0.1:8080 to use your application" 90 | kubectl port-forward $POD_NAME 8080:80 91 | ``` 92 | 93 | 这里的注释是由Chart中的`templates/NOTES.txt`提供的。我们注意到原始的NOTES中,所写的`"app={{ template "my-hello-world.name" . }},release={{ .Release.Name }}"`和我们的deployment.yaml中所写的配置不太一样。我们可以把它改成`"app.kubernetes.io/name={{ template "my-hello-world.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"`,将values.yaml中的`version`更新成`0.1.1`。然后重新打包Chart(运行`helm package`)。得到新的my-hello-world-0.1.1.tgz之后,重新安装Chart(运行`helm install my-hello-world-chart-test3 my-hello-world-0.1.1.tgz --set Username="New Chart"`),就能看到更新过后的NOTES了。 94 | 95 | ```bash 96 | NAME: my-hello-world-chart-test3 97 | LAST DEPLOYED: 2019-07-10 14:02:55.321468411 +0800 CST m=+0.091032750 98 | NAMESPACE: default 99 | STATUS: deployed 100 | 101 | NOTES: 102 | 1. Get the application URL by running these commands: 103 | export POD_NAME=$(kubectl get pods -l "app.kubernetes.io/name=my-hello-world,app.kubernetes.io/instance=my-hello-world-chart-test3" -o jsonpath="{.items[0].metadata.name}") 104 | echo "Visit http://127.0.0.1:8080 to use your application" 105 | kubectl port-forward $POD_NAME 8080:80 106 | ``` 107 | 108 | #### 其他 109 | 110 | Helm Chart还有诸如dependency等其他功能,更加详细的资料可以参考Helm官方文档的[相关章节](https://v3.helm.sh/docs/topics/chart_template_guide/)。 111 | --------------------------------------------------------------------------------