# 基本使用

## 基本功能-serving

本小节会按照“从零开始写一个 go 应用”的流程，来梳理功能

### 编写 go 代码

```
$ cat hello.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello, world!\n")
}

func main() {
    http.HandleFunc("/", handler)
    port := "8080"
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
```

在 serving 体系下编写 go 应用，跟常规 go web 开发并无区别

### 构造镜像

```
$ cat Dockerfile
FROM golang:1.13 as builder
WORKDIR /app
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -v -o server

FROM alpine:3
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
CMD ["/server"]

$ docker build . -t dev.local/hello-go:serving
```

常规编译，没什么要补充的

### 部署应用

```
$ cat service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hello-go
spec:
  template:
    spec:
      containers:
      - image: dev.local/hello-go:serving

$ kubectl apply -f service.yaml
```

创建 ksvc 涉及的完整流程是：

*   创建了 ksvc

    ```
      kubectl get ksvc
      NAME       URL                                   LATESTCREATED    LATESTREADY      READY   REASON
      hello-go   http://hello-go.default.example.com   hello-go-6wdlw   hello-go-6wdlw   True
    ```
*   controller 依据 ksvc 创建了对应的 rt、cfg

    ```
      $ kubectl get rt
      NAME       URL                                   READY   REASON
      hello-go   http://hello-go.default.example.com   True
      $ kubectl get cfg
      NAME       LATESTCREATED    LATESTREADY      READY   REASON
      hello-go   hello-go-6wdlw   hello-go-6wdlw   True
    ```
*   controller 依据 cfg 创建了对应的 rev

    ```
      $ kubectl get rev
      NAME             CONFIG NAME   K8S SERVICE NAME   GENERATION   READY   REASON
      hello-go-6wdlw   hello-go      hello-go-6wdlw     1            True
    ```
*   controller 依据 rt 创建了 king

    ```
      $ kubectl get king
      NAME       READY   REASON
      hello-go   True
    ```
*   networking-istio 依据 king 创建了 VirtualService(vs)，后者是 istio CRD，接下来就是 istio 的逻辑了，这里就不再展开

    ```
      $ kubectl get vs
      NAME               GATEWAYS                                                                          HOSTS                                                                                                     AGE
      hello-go-ingress   [knative-serving/cluster-local-gateway knative-serving/knative-ingress-gateway]   [hello-go.default hello-go.default.example.com hello-go.default.svc hello-go.default.svc.cluster.local]   2d20h
      hello-go-mesh      [mesh]                                                                            [hello-go.default hello-go.default.svc hello-go.default.svc.cluster.local]                                2d20h
    ```
*   controller 依据 rev 创建了 kpa

    ```
      $ kubectl get kpa
      NAME             DESIREDSCALE   ACTUALSCALE   READY   REASON
      hello-go-6wdlw   0              0             False   NoTraffic
    ```
*   autoscaler 依据 kpa 创建了 sks

    ```
      $ kubectl get sks
      NAME             MODE    ACTIVATORS   SERVICENAME      PRIVATESERVICENAME       READY     REASON
      hello-go-6wdlw   Proxy   2            hello-go-6wdlw   hello-go-6wdlw-private   Unknown   NoHealthyBackends
    ```
* controller 依据  rev 创建了 deploy
*   svc 的创建要稍微麻烦一些，实际上创建了3个 service

    ```
      $ kubectl get svc
      NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP                                            PORT(S)                             AGE
      hello-go                 ExternalName   <none>           cluster-local-gateway.istio-system.svc.cluster.local   <none>                              2d21h
      hello-go-6wdlw           ClusterIP      10.96.213.237    <none>                                                 80/TCP                              2d21h
      hello-go-6wdlw-private   ClusterIP      10.108.110.187   <none>                                                 80/TCP,9090/TCP,9091/TCP,8022/TCP   2d21h
    ```

    我们的用例只用到了其中一个，其它的如 hello-go，是用于外部域名到内部域名映射用的，我们用例中不涉及
*   注意一些细节，比如：svc 是指向 activator 的；部分 svc 没有 selector（Knative 自己来同步）

    ```
      $ kubectl get ep hello-go-6wdlw
      NAME             ENDPOINTS          AGE
      hello-go-6wdlw   10.244.0.71:8012   2d21h
      $ kubectl -n knative-serving get pod activator-68cbc9b5c7-6fsc2 -o wide
      NAME                         READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
      activator-68cbc9b5c7-6fsc2   1/1     Running   0          5d17h   10.244.0.71   n227-020-128   <none>           <none>
    ```

### 访问应用

```
# 查看应用 URL 后访问
$ kubectl get ksvc
NAME       URL                                   LATESTCREATED    LATESTREADY      READY   REASON
hello-go   http://hello-go.default.example.com   hello-go-6wdlw   hello-go-6wdlw   True
$ curl -H "Host: hello-go.default.example.com" http://$INTERNAL_INGRESS_HOST
hello, world!
```

访问应用涉及的完整流程是：

* 访问请求发送到 istio gateway
* istio 会完成域名到 k8s svc 的解析，访问请求转发到 activator
* activator 触发 deploy 扩容
* 访问新建 pod，返回响应

如果是较为频繁的访问，则还会触发其它的逻辑

```
$ hey -n 1000000 -c 300 -m GET -host hello-go.default.example.com http://$INTERNAL_INGRESS_HOST
```

* autoscaler 更新 sks
*   controller 更新 ep，由 activator 改为应用 pod

    ```
      $ kubectl get ep hello-go-6wdlw
      NAME                     ENDPOINTS                                                            AGE
      hello-go-6wdlw           10.244.0.102:8012,10.244.0.103:8012,10.244.0.104:8012 + 1 more...    2d22h
    ```

### 小结

因为 serving 涉及的 CRD 比较多，这里再把它们统一放这里梳理一下，一图胜千言：

![crd-serving](https://1596382671-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MG3KlFGBf3gifLxQm3D%2Fsync%2Ff5e50b8b4e05f667d917d861e7edf8f0c3776d72.png?generation=1598874776076718\&alt=media)

## 基本功能-eventing

本小节会按照“从零开始写一个 go 应用”的流程，来梳理功能

### 编写 go 代码

```
$ cat hello.go
package main

import (
    "context"
    "log"

    cloudevents "github.com/cloudevents/sdk-go/v2"
)

type HelloWorld struct {
    Msg string `json:"msg,omitempty"`
}

func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
    data := &HelloWorld{}
    if err := event.DataAs(data); err != nil {
        log.Printf("Error while extracting cloudevent Data: %s\n", err.Error())
        return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
    }
    log.Printf("Hello World Message from received event %q", data.Msg)
    return nil, nil
}

func main() {
    c, err := cloudevents.NewDefaultClient()
    if err != nil {
        log.Fatalf("failed to create client, %v", err)
    }
    log.Fatal(c.StartReceiver(context.Background(), receive))
}

$ cat go.mod
module hello-eventing
go 1.13
require github.com/cloudevents/sdk-go/v2 v2.0.0-RC2
```

在 eventing 体系下编写 go 应用，需要借助 cloudevents sdk

### 构造镜像

```
$ cat Dockerfile
FROM golang:1.13 as builder
WORKDIR /app
ENV GOPROXY https://goproxy.cn,direct
COPY go.* ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -v -o server

FROM alpine:3
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
CMD ["/server"]

$ docker build . -t dev.local/hello-go:eventing
```

常规编译，没什么要补充的

### 部署应用

```
$ cat app.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: knative-samples
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-go
  namespace: knative-samples
spec:
  replicas: 1
  selector:
    matchLabels: &labels
      app: hello-go
  template:
    metadata:
      labels: *labels
    spec:
      containers:
      - name: hello-go
        image: dev.local/hello-go:eventing
---
apiVersion: v1
kind: Service
metadata:
  name: hello-go
  namespace: knative-samples
spec:
  selector:
    app: hello-go
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
```

这里部署的都是 k8s 原生对象，没什么要补充的

### 创建 broker

```
$ cat broker.yaml
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
  name: default
  namespace: knative-samples
spec: {}

$ kubectl apply -f broker.yaml
```

创建 broker 涉及的完整流程是：

*   创建了 broker

    ```
      $ kubectl -n knative-samples get broker
      NAME      URL                                                                                AGE     READY   REASON
      default   http://broker-ingress.knative-eventing.svc.cluster.local/knative-samples/default   4m31s   True
    ```
*   mt-broker-controller 将 channel 地址填充到 broker status

    ```
      $ kubectl -n knative-samples get broker default -o yaml | grep channelAddress
          knative.dev/channelAddress: http://default-kne-trigger-kn-channel.knative-samples.svc.cluster.local
    ```

    而这个地址实际上也就是指向了 imc-dispatcher

    ```
      $ kubectl -n knative-samples get svc
      NAME                             TYPE           CLUSTER-IP    EXTERNAL-IP                                         PORT(S)   AGE
      default-kne-trigger-kn-channel   ExternalName   <none>        imc-dispatcher.knative-eventing.svc.cluster.local   <none>    16h
    ```
*   eventing-controller 依据 broker 创建了对应的 imc

    ```
      $ kubectl -n knative-samples get imc
      NAME                  URL                                                                       AGE     READY   REASON
      default-kne-trigger   http://default-kne-trigger-kn-channel.knative-samples.svc.cluster.local   4m35s   True
    ```

    imc 中将 broker-filter 作为 subscriber

    ```
      $ kubectl -n knative-samples get imc default-kne-trigger -o yaml | grep subscriberUri
          subscriberUri: http://broker-filter.knative-eventing.svc.cluster.local/triggers/knative-samples/hello-go/b0532795-1042-4459-898f-d63afea8307d
    ```

### 创建 trigger

```
$ cat trigger.yaml
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: hello-go
  namespace: knative-samples
spec:
  broker: default
  filter:
    attributes:
      type: dev.knative.samples.helloworld
      source: dev.knative.samples/helloworldsource
  subscriber:
    ref:
      apiVersion: v1
      kind: Service
      name: hello-go

$ kubectl apply -f trigger.yaml
```

创建 trigger 涉及的完整流程是：

*   创建了 trigger

    ```
      $ kubectl -n knative-samples get trigger
      NAME       BROKER    SUBSCRIBER_URI                                       AGE     READY   REASON
      hello-go   default   http://hello-go.knative-samples.svc.cluster.local/   2m40s   True
    ```
*   mt-broker-controller 将应用地址填充到 trigger.status

    ```
      kubectl -n knative-samples get trigger hello-go -o yaml | grep subscriberUri
    ```

### 访问应用

```
$ kubectl -n knative-samples run -it curl --image=radial/busyboxplus:curl
[ root@curl-69c656fd45-7v77f:/ ]$ curl "http://broker-ingress.knative-eventing.svc.cluster.local/knative-samples/default" -X POST -H "Ce-Id: xxx" -H "Ce-Specversion: 1.0" -H "Ce-Type: dev.knative.samples.helloworld" -H
"Ce-Source: dev.knative.samples/helloworldsource" -H "Content-Type: application/json" -d '{"msg":"hello, world!"}'
[ root@curl-69c656fd45-7v77f:/ ]$ exit
$ kubectl -n knative-samples logs -l app=hello-go
2020/09/02 12:20:09 Hello World Message from received event "hello, world!"
```

访问应用涉及的完整流程是：

* 访问请求发送到 broker-ingress
* broker-ingress 从 broker.status 里获取 channel 地址，转发 event 到 imc-dispatcher
* imc-dispatcher 从 channel.spec 里获取 subscriber 地址，转发 event 到 broker-dispatcher
* broker-dispatcher 从 trigger.spec 里获取 filter 配置，对 event 进行过滤
* broker-dispatcher 从 trigger.status 里获取 subscriber 地址，转发 event 到应用
