diff --git a/SUMMARY.md b/SUMMARY.md index bd65d1b5a..8b4fd1a1d 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -240,6 +240,7 @@ * [Knative](usecases/knative.md) * [云原生应用标准](usecases/cloud-native-app-standard.md) * [OAM(开放应用模型)](usecases/oam.md) + * [Crossplane](usecases/crossplane.md) * [边缘计算](usecases/edge-computing.md) * [人工智能](usecases/ai.md) diff --git a/images/wordpress.jpg b/images/wordpress.jpg new file mode 100644 index 000000000..aff688d5f Binary files /dev/null and b/images/wordpress.jpg differ diff --git a/manifests/charts/oam-core-resources/.helmignore b/manifests/charts/oam-core-resources/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/manifests/charts/oam-core-resources/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/manifests/charts/oam-core-resources/Chart.yaml b/manifests/charts/oam-core-resources/Chart.yaml new file mode 100644 index 000000000..18311457a --- /dev/null +++ b/manifests/charts/oam-core-resources/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +name: oam-core-resources +description: A Helm chart for OAM Core Resources Controller + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +version: 0.1.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. +appVersion: 0.2.0 diff --git a/manifests/charts/oam-core-resources/templates/_helpers.tpl b/manifests/charts/oam-core-resources/templates/_helpers.tpl new file mode 100644 index 000000000..afc20392f --- /dev/null +++ b/manifests/charts/oam-core-resources/templates/_helpers.tpl @@ -0,0 +1,86 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "oam-core-resources.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name, otherwise, it will be append to the chart name +*/}} +{{- define "oam-core-resources.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" $name .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "oam-core-resources.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Use webhook argument +*/}} +{{- define "oam-core-resources.use-webhook" -}} +{{- if .Values.useWebhook -}} +{{- "--enable-webhook=true" -}} +{{- else -}} +{{- "--enable-webhook=false" -}} +{{- end -}} +{{- end -}} + +{{/* +Use create namespace +*/}} +{{- define "oam-core-resources.createNamespace" -}} +{{- if eq .Release.Namespace "default" -}} +{{- false -}} +{{- else -}} +{{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "oam-core-resources.labels" -}} +helm.sh/chart: {{ include "oam-core-resources.chart" . }} +{{ include "oam-core-resources.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "oam-core-resources.selectorLabels" -}} +app.kubernetes.io/name: {{ include "oam-core-resources.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "oam-core-resources.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "oam-core-resources.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/manifests/charts/oam-core-resources/templates/oam-local-controller.yaml b/manifests/charts/oam-core-resources/templates/oam-local-controller.yaml new file mode 100644 index 000000000..1f60f8440 --- /dev/null +++ b/manifests/charts/oam-core-resources/templates/oam-local-controller.yaml @@ -0,0 +1,141 @@ +--- +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "oam-core-resources.serviceAccountName" . }} + labels: + {{ include "oam-core-resources.labels" . | nindent 4 }} + {{- end }} +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: "cluster-admin" +subjects: + - kind: ServiceAccount + name: {{ include "oam-core-resources.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + +--- +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - update + - patch + - apiGroups: + - "" + resources: + - events + verbs: + - create + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: + - kind: ServiceAccount + name: {{ include "oam-core-resources.serviceAccountName" . }} + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oam-core-resources.fullname" . }} + labels: + {{- include "oam-core-resources.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "oam-core-resources.selectorLabels" . | nindent 6 }} + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + {{- include "oam-core-resources.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "oam-core-resources.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Release.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + args: + - "--metrics-addr=:8080" + - "--enable-leader-election" + - {{ include "oam-core-resources.use-webhook" . | quote }} + image: {{ .Values.image.repository }} + imagePullPolicy: {{ quote .Values.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{ if .Values.useWebhook }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: {{ .Values.certificate.mountPath }} + name: tls-cert + readOnly: true + {{ end }} + - name: kube-rbac-proxy + image: jimmysong/kubebuilder-kube-rbac-proxy:v0.4.1 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=10" + ports: + - containerPort: 8443 + name: https + volumes: + - name: tls-cert + secret: + defaultMode: 420 + secretName: {{ .Values.certificate.secretName | quote }} + terminationGracePeriodSeconds: 10 + {{- with .Values.nodeSelector }} + nodeSelector: + {{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/manifests/charts/oam-core-resources/templates/oam-local-webhooks.yaml b/manifests/charts/oam-core-resources/templates/oam-local-webhooks.yaml new file mode 100644 index 000000000..aca3a5823 --- /dev/null +++ b/manifests/charts/oam-core-resources/templates/oam-local-webhooks.yaml @@ -0,0 +1,107 @@ +{{- if .Values.useWebhook -}} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "oam-core-resources.fullname" . }} + labels: + {{- include "oam-core-resources.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: 9443 + protocol: TCP + name: http + selector: + {{- include "oam-core-resources.selectorLabels" . | nindent 4 }} + +--- +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for breaking changes +apiVersion: cert-manager.io/v1alpha2 +kind: Issuer +metadata: + name: {{ .Values.certificate.issuerName | quote }} +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1alpha2 +kind: Certificate +metadata: + name: {{ .Values.certificate.certificateName }} +spec: + dnsNames: + - {{ include "oam-core-resources.fullname" . }}.{{ .Release.Namespace }}.svc + - {{ include "oam-core-resources.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ .Values.certificate.issuerName | default "selfsigned-issuer" | quote }} + secretName: {{ .Values.certificate.secretName | quote }} + + +--- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ .Values.certificate.certificateName }} +webhooks: +- clientConfig: + caBundle: Cg== + service: + name: {{ include "oam-core-resources.fullname" . }} + namespace: {{ .Release.Namespace }} + path: /validate-core-oam-dev-v1alpha2-manualscalertrait + port: {{ .Values.service.port }} + name: manualscalertrait.validate.core.oam.dev + rules: + - apiGroups: + - core.oam.dev + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - manualscalertraits + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: ["v1", "v1beta1"] + timeoutSeconds: 5 + +--- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ .Values.certificate.certificateName }} +webhooks: +- clientConfig: + caBundle: Cg== + service: + name: {{ include "oam-core-resources.fullname" . }} + namespace: {{ .Release.Namespace }} + path: /mutate-core-oam-dev-v1alpha2-manualscalertrait + port: {{ .Values.service.port }} + name: manualscalertrait.mutate.core.oam.dev + rules: + - apiGroups: + - core.oam.dev + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - manualscalertraits + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: ["v1", "v1beta1"] + timeoutSeconds: 5 +{{- end -}} \ No newline at end of file diff --git a/manifests/charts/oam-core-resources/templates/tests/test-connection.yaml b/manifests/charts/oam-core-resources/templates/tests/test-connection.yaml new file mode 100644 index 000000000..2c3060d5d --- /dev/null +++ b/manifests/charts/oam-core-resources/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "oam-core-resources.fullname" . }}-test-connection" + labels: +{{ include "oam-core-resources.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "oam-core-resources.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/manifests/charts/oam-core-resources/values.yaml b/manifests/charts/oam-core-resources/values.yaml new file mode 100644 index 000000000..55607e2c9 --- /dev/null +++ b/manifests/charts/oam-core-resources/values.yaml @@ -0,0 +1,58 @@ +# Default values for oam-core-resources. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 +useWebhook: false +image: + repository: oamdev/core-resource-controller:v0.5 #crossplane/addon-oam-kubernetes-local:v0.1 + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" +ingress: + enabled: false + +serviceAccount: + # Specifies whether a service account should be created + create: true + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 443 + +resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 20Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# certificate related to the webhook +certificate: + certificateName: serving-cert + issuerName: selfsigned-issuer + secretName: webhook-server-cert + mountPath: /etc/k8s-webhook-certs \ No newline at end of file diff --git a/manifests/oam/cluster-role.yaml b/manifests/oam/cluster-role.yaml new file mode 100644 index 000000000..5e8de7570 --- /dev/null +++ b/manifests/oam/cluster-role.yaml @@ -0,0 +1,25 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: crossplane-oam +rules: +- apiGroups: + - apps + resources: + - statefulsets + - deployments + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: oam-example-catalog +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: crossplane-oam +subjects: + - kind: ServiceAccount + name: crossplane-oam + namespace: crossplane-system diff --git a/manifests/oam/containerized-workload/sample_application_config.yaml b/manifests/oam/containerized-workload/sample_application_config.yaml new file mode 100644 index 000000000..07881ac4e --- /dev/null +++ b/manifests/oam/containerized-workload/sample_application_config.yaml @@ -0,0 +1,20 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: ApplicationConfiguration +metadata: + name: example-appconfig +spec: + components: + - componentName: example-component + parameterValues: + - name: instance-name + value: example-appconfig-workload + - name: image + value: wordpress:php7.2 + traits: + - trait: + apiVersion: core.oam.dev/v1alpha2 + kind: ManualScalerTrait + metadata: + name: example-appconfig-trait + spec: + replicaCount: 3 \ No newline at end of file diff --git a/manifests/oam/containerized-workload/sample_component.yaml b/manifests/oam/containerized-workload/sample_component.yaml new file mode 100644 index 000000000..fab23e371 --- /dev/null +++ b/manifests/oam/containerized-workload/sample_component.yaml @@ -0,0 +1,23 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Component +metadata: + name: example-component +spec: + workload: + apiVersion: core.oam.dev/v1alpha2 + kind: ContainerizedWorkload + spec: + containers: + - name: wordpress + image: wordpress:4.6.1-apache + ports: + - containerPort: 80 + name: wordpress + parameters: + - name: instance-name + required: true + fieldPaths: + - metadata.name + - name: image + fieldPaths: + - spec.containers[0].image \ No newline at end of file diff --git a/manifests/oam/containerized-workload/sample_trait_definition.yaml b/manifests/oam/containerized-workload/sample_trait_definition.yaml new file mode 100644 index 000000000..2b0ef906e --- /dev/null +++ b/manifests/oam/containerized-workload/sample_trait_definition.yaml @@ -0,0 +1,7 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: manualscalertraits.core.oam.dev +spec: + definitionRef: + name: manualscalertraits.core.oam.dev \ No newline at end of file diff --git a/manifests/oam/containerized-workload/sample_workload_definition.yaml b/manifests/oam/containerized-workload/sample_workload_definition.yaml new file mode 100644 index 000000000..ad79b971d --- /dev/null +++ b/manifests/oam/containerized-workload/sample_workload_definition.yaml @@ -0,0 +1,12 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: containerizedworkloads.core.oam.dev +spec: + definitionRef: + name: containerizedworkloads.core.oam.dev + childResourceKinds: + - apiVersion: apps/v1 + kind: Deployment + - apiVersion: v1 + kind: Service \ No newline at end of file diff --git a/manifests/oam/food-truck.yaml b/manifests/oam/food-truck/food-truck.yaml similarity index 100% rename from manifests/oam/food-truck.yaml rename to manifests/oam/food-truck/food-truck.yaml diff --git a/usecases/crossplane.md b/usecases/crossplane.md new file mode 100644 index 000000000..2c5a35cc6 --- /dev/null +++ b/usecases/crossplane.md @@ -0,0 +1,135 @@ +# Crossplane + +本文主要为读者介绍 Crossplane 是什么及如何结合 OAM 来管理 Kubernetes 应用。 + +## Crossplane 是什么? + +[Crossplane](https://crossplane.io/) 是一个开源的 Kubernetes 插件,可以使用 kubectl 配置和管理基础设施、服务和应用。它的意义在使用 Kubernetes 风格的 API 统一了云基础设施和应用程序的管理。 + +该项目是由 [Upbound](https://upbound.io/) 公司于 2018 年发起,开源社区主要参与者有微软、阿里巴巴、Gitlab、红帽等。 + +## Crossplane 的特性 + +下面几点是 Crossplane 的基本特性。 + +**支持自定义 API(CRD)** + +在 Crossplane 提供的 CRD 之上构建自己的内部基础设施抽象。您的自定义 API 可以包含策略保护,隐藏基础设施的复杂性,并使其安全地供应用程序消费。 + +**支持 OAM** + +Crossplane 实现了 [OAM(开放应用模型)](./oam.md),帮助统一应用和基础架构管理,以团队为中心的流程。通过 Crossplane 和 OAM,应用和基础架构配置可以共存,并使用相同的工具进行部署。 + +**支持混合云** + +无论你使用的是 EKS、AKS、GKE、ACK、PKS 中的单个 Kubernetes 集群,还是 Rancher 或 Anthos 这样的多集群管理器,Crossplane 都能很好地与它们集成。Crossplane 安装到任何现有的集群中,暴露出 CRD 和跨基础设施和服务提供商的标准 API,使供应和管理变得轻而易举。 + +## 准备条件 + +在安装使用 Crossplane 之前需要确保您的系统满足以下要求: + +- Kubernetes v1.16+ +- Helm 3 +- [Crossplane](https://github.com/crossplane/crossplane) v0.11+ + +## 安装 Crossplane + +在准备好以上条件之后,执行下面的命令安装 Crossplane。 + +```bash +kubectl create namespace crossplane-system +helm repo add crossplane-alpha https://charts.crossplane.io/alpha +helm install crossplane --namespace crossplane-system crossplane-alpha/crossplane +``` + +该步骤会创建一个 `crossplane-system` 的 namespace 和如下的 CRD。 + +```ini +# OAM 的 CRD +applicationconfigurations.core.oam.dev +components.core.oam.dev +containerizedworkloads.core.oam.dev +manualscalertraits.core.oam.dev +scopedefinitions.core.oam.dev +traitdefinitions.core.oam.dev +workloaddefinitions.core.oam.dev +buckets.storage.crossplane.io + +# crossplane 原生的 CRD +clusterpackageinstalls.packages.crossplane.io +compositions.apiextensions.crossplane.io +infrastructuredefinitions.apiextensions.crossplane.io +infrastructurepublications.apiextensions.crossplane.io +kubernetesapplicationresources.workload.crossplane.io +kubernetesapplications.workload.crossplane.io +kubernetesclusters.compute.crossplane.io +kubernetestargets.workload.crossplane.io +machineinstances.compute.crossplane.io +mysqlinstances.database.crossplane.io +nosqlinstances.database.crossplane.io +packageinstalls.packages.crossplane.io +packages.packages.crossplane.io +postgresqlinstances.database.crossplane.io +providers.kubernetes.crossplane.io +redisclusters.cache.crossplane.io +stackdefinitions.packages.crossplane.io +``` + +安装 OAM controller。 + +```bash +kubectl create namespace oam-system +helm install controller -n oam-system ./manifests/charts/oam-core-resources/ +``` + +## 部署示例 + +部署一个 WordPress 示例应用。 + +```bash +kubectl apply -f manifests/oam/containerized-workload +``` + +该应用采用 [OAM 规范定义](../cloud-native/define-cloud-native-app.md),包括 Workload、Component、Trait 和 ApplicationConfiguration,感兴趣的读者可以到 [manifests/oam/containerized-workload](https://github.com/rootsongjc/kubernetes-handbook/tree/master/manifests/oam/containerized-workload) 目录下查看,其中 `sample_workload_definition.yaml` 文件的内容如下: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: containerizedworkloads.core.oam.dev +spec: + definitionRef: + name: containerizedworkloads.core.oam.dev + childResourceKinds: + - apiVersion: apps/v1 + kind: Deployment + - apiVersion: v1 + kind: Service +``` + +此处定义了一个 `containerizedworkloads.core.oam.dev` Workload,其中添加一个 `childResourceKinds` 字段。目前,`workloadDefinition` 不过是真正的 CRD 的一个注册器。Workload 所有者在向 OAM 系统注册控制器时填写这个字段,声明其工作负载控制器实际生成的 Kubernetes 资源类型。在本示例中该 Workload 生成的是 Kubernetes 的 Deployment 和 Service。 + +关于此处配置的详细原理请参考 [Traits and workloads interaction mechanism in OAM](https://github.com/crossplane/oam-kubernetes-runtime/blob/master/design/one-pager-trait-workload-interaction-mechanism.md)。 + +## 验证 + +在部署了上面的示例后,会创建一个名为 `example-appconfig-workload` 的 Deployment 和名为 `example-appconfig-workload` 的 Service。 + +```bash +$ kubectl get deployment +NAME READY UP-TO-DATE AVAILABLE AGE +example-appconfig-workload 3/3 3 3 9h + +$ kubectl get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +example-appconfig-workload NodePort 10.99.30.250 80:31557/TCP 9h +``` + +查看该 Service 的 NodePort(本示例中为 31557) 及 minikube 的 IP 地址(本示例中为 `192.168.64.2`)。在浏览器中访问 `http://192.168.64.2:31557` 即可看到 Workpress 的启动页面。 + +![Wordpress 页面](../images/wordpress.jpg) + +## 参考 + +- [crossplane/addon-oam-kubernetes-local - github.com](https://github.com/crossplane/addon-oam-kubernetes-local) +- [Traits and workloads interaction mechanism in OAM - github.com](https://github.com/crossplane/oam-kubernetes-runtime/blob/master/design/one-pager-trait-workload-interaction-mechanism.md) \ No newline at end of file