1. Installation

1.1 Check Version

# Knavtive Server 버젼 확인
$ kubectl get namespace knative-serving -o 'go-template={{index .metadata.labels "serving.knative.dev/release"}}'
v0.11.2

# Knative Eventing 버젼 확인 
$ kubectl get namespace knative-eventing -o 'go-template={{index .metadata.labels "eventing.knative.dev/release"}}'

1.2 Install Knative CLI

2020-09-15-Knative
$ wget https://storage.googleapis.com/knative-nightly/client/latest/kn-linux-amd64
$ chmod 110 kn-linux-amd64
$ sudo mv kn-linux-amd64 /usr/local/bin/kn

1.3 Delete Existing Serving Component

강제로 업그레이드시 apply를 delete로 변경하고 이후 apply진행합니다. (서비스 안하고 있다는 전제)
일단 삭제이유는 kubeflow로 설치이후 버젼 업그레이드가 목적입니다.
귀찮아서 업그레이드 방식 따르지 않고 그냥 다 삭제해버리고 다시 깔아버리는 방법입니다.
바로 아래 코드는 서비스를 안하고 있을경우 강제로 업그레이드 하기 위해서 먼저 삭제부터 하는 코드 입니다.
주의하세요!!

$ kubectl get namespaces knative-serving -o json | jq '.spec.finalizers'=null | kubectl apply -f -
$ kubectl delete --filename https://github.com/knative/serving/releases/download/v0.18.0/serving-crds.yaml
$ kubectl delete --filename https://github.com/knative/serving/releases/download/v0.18.0/serving-core.yaml
$ kubectl get namespaces  knative-serving 
NAME              STATUS        AGE
knative-serving   Terminating   44m

위를 확인해보면 Terminating에서 멈춰서 더이상 진행이 되지 않습니다.
Finelizer 부분을 설정해줘야 합니다.
참고로 그냥 edit으로 안됩니다.

$ kubectl get namespaces knative-serving -o json > knative-serving.json
$ vi knative-serving.json

안에 보면 finalizers 라고 있을겁니다.

    "spec": {
        "finalizers": [
            "kubernetes"
        ]
    }

finalizers 부분을 삭제하고 저장합니다.

    "spec": {
        "finalizers": [
        ]
    }

주소를 알아내고, 편집한 json 파일로 대체를 합니다.
이때 알아낸 selfLink의 주소 뒤에 /finalize 를 붙여줘야 합니다.

$ kubectl get namespace knative-serving -o json | jq .metadata.selfLink
"/api/v1/namespaces/knative-serving"

$ kubectl replace --raw "/api/v1/namespaces/knative-serving/finalize" -f ./knative-serving.json

이후 kubectl get namespaces 로 확인해보면 삭제되어 있을겁니다.

1.4 Install Serving Component

Knative Component를 설치합니다.

$ kubectl apply -f https://github.com/knative/operator/releases/download/v0.18.0/operator.yaml

Serving을 설치합니다.

$ kubectl apply --filename https://github.com/knative/serving/releases/download/v0.18.0/serving-crds.yaml
$ kubectl apply --filename https://github.com/knative/serving/releases/download/v0.18.0/serving-core.yaml

Istio Controller를 설치 합니다.

$ kubectl apply --filename https://github.com/knative/net-istio/releases/download/v0.18.0/release.yaml
$ kubectl get svc istio-ingressgateway  -n istio-system
NAME                   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                                                                                                      AGE
istio-ingressgateway   NodePort   10.100.144.52   <none>        15020:32290/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:32607/TCP,15030:32547/TCP,15031:30376/TCP,15032:32370/TCP,15443:32567/TCP   80m

여기서 AWS의 경우 NodePort로 나오고 EXTERNAL-IP가 none값으로 되어 있을겁니다.
AWS EKS 에서는 LoadBalancer를 따로 잡아줘야지 실제 Elastic LoadBalancer가 잡힙니다.

$ kubectl edit svc istio-ingressgateway  -n istio-system

type: NodePorttype: LoadBalancer로 변경해줍니다.
만약 AWS EKS를 사용시 EC2 -> Load Balancers -> 기존 사용하던 Load Balancer를 클릭 -> DNS Name 을 복사합니다.

아래와 같이 변경합니다.

  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - hostname: a5af7519611fe4e7f90aa325e747cb1f-687089119.us-east-2.elb.amazonaws.com

이후 다시 service를 확인해보면 EXTERNAL-IP가 잡혀있게 됩니다.

$ kubectl --namespace istio-system get service istio-ingressgateway
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                        PORT(S)                    
istio-ingressgateway   LoadBalancer   10.100.200.170   ****.us-east-2.elb.amazonaws.com   80:32051/TCP,443:31101/TCP

Knative Components 들이 모두 잘 작동하는지 점검합니다.

$ kubectl get pods -n knative-serving
NAME                                READY   STATUS    RESTARTS   AGE
activator-56cf848f9d-k79h8          1/1     Running   0          5m40s
autoscaler-67c75d8566-4ln8c         1/1     Running   0          5m39s
controller-6568f84b8b-fqhwc         1/1     Running   0          5m37s
istio-webhook-6594d8d54c-7mbz8      1/1     Running   0          4m22s
networking-istio-568ffff747-fmxfk   1/1     Running   0          4m22s
webhook-785c5879fb-kpdp6            1/1     Running   0          5m36s

$ kubectl get deployments -n knative-serving
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
activator          1/1     1            1           17m
autoscaler         1/1     1            1           17m
controller         1/1     1            1           17m
istio-webhook      1/1     1            1           5m6s
networking-istio   1/1     1            1           5m6s
webhook            1/1     1            1           17m

1.5 Install Eventing Component

Custom Resource Definitions (CRD) 그리고 핵심 core components를 설치 합니다.

$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.18.0/eventing-crds.yaml
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.18.0/eventing-core.yaml

Message Channel을 설치 (아래는 in-memory standalone 버젼. 그외 Kafka, GCP Pub/Sub 등등 있음)

$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.18.0/in-memory-channel.yaml
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.18.0/mt-channel-broker.yaml

모두 정상 작동 하는지 확인

$ kubectl get pods --namespace knative-eventing
NAME                                    READY   STATUS    RESTARTS   AGE
eventing-controller-6fc9c6cfc4-h79h7    1/1     Running   1          2m58s
eventing-webhook-667c8f6dc4-45w8v       1/1     Running   4          2m56s
imc-controller-6c4f87765c-k4snr         1/1     Running   0          2m
imc-dispatcher-74dcf4647f-clspk         1/1     Running   0          119s
mt-broker-controller-6d789b944d-f59xg   1/1     Running   0          32s
mt-broker-filter-6bbcc67bc5-rfssc       1/1     Running   0          35s
mt-broker-ingress-64987f6f4-cv8qx       1/1     Running   0          33s

2. Configuration

2.1 Custom Domain Setup

Knative Serving Route는 기본값으로 example.com 을 기본 도메인으로 사용합니다.
이후 KFServing을 할때도 모두 example.com 으로 나오기 때문에 이 부분의 변경이 반드시 필요 합니다.

$ kubectl edit cm config-domain --namespace knative-serving

처음 열면 아래와 같은 형식으로 생겼는데..
example:_| 로 시작하는 부분을 모두 삭제 합니다.
example.com 까지 모두 삭제 했으면, example.com 도메인을 변경합니다.

apiVersion: v1
data:
  _example: |
    ################################
    #                              #
    #    EXAMPLE CONFIGURATION     #
    #                              #
    ################################
    # ...
    example.com: |
kind: ConfigMap

변경이후 다음과 같습니다.

apiVersion: v1
data:
  example.org: |
    selector:
      app: nonprofit
  incredible.ai: ""
  svc.cluster.local: |
    selector:
      app: secret
kind: ConfigMap

3. Getting Started

3.1 Hello World in Python

Python App

cat <<EOF > app.py
import os
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    target = os.environ.get('TARGET', 'World')
    return 'Hello {}!\n'.format(target)
if __name__ == "__main__":
    app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
EOF

Dockerfile & Build

cat <<EOF > Dockerfile
FROM python:3.7-slim
ENV PYTHONUNBUFFERED=True
ENV APP_HOME=/app
WORKDIR \$APP=HOME
COPY . ./
RUN pip install Flask gunicorn
CMD exec gunicorn --bind :\$PORT --workers 1 --threads 8 --timeout 0 app:app
EOF
$ docker login
$ docker build -t andersonjo/knative-hello-world .
$ docker push andersonjo/knative-hello-world

Service & Deploy
andersonjo 부분을 Docker Hub 아이디로 변경 필요

cat <<EOF > service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-python
  namespace: default
spec:
  template:
    spec:
      containers:
      - image: docker.io/andersonjo/knative-hello-world 
        env:
        - name: TARGET
          value: "Python Sample v1"
EOF
$ kubectl apply -f service.yaml
$ kubectl get ksvc helloworld-python
NAME                URL                                            LATESTCREATED             LATESTREADY               READY   REASON
helloworld-python   http://helloworld-python.default.ai.platform   helloworld-python-69cbt   helloworld-python-69cbt   True    
$ INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
$ curl -H "Host:helloworld-python.default.ai.platform" $INGRESS_HOST
Hello Python Sample v1!

3. Autoscale

3.1 Autoscaler Types

  1. Knative Pod Autoscaler (KPA)
    • Knative Serving Core의 한 부분이며, 디폴트값이 사용이다.
    • 트래픽이 없을경우 0으로 scale 하는 기능도 있음 (scale down)
    • CPU 기반의 autoscaling은 지원하지 않음
  2. Horizontal Pod Autoscaler (HPA)
    • Knative Serving Core에서 제공되는 기능이 아니며, Knative Serving 설치이후 따로 설정해줘야 함
    • 트래픽이 없을 경우 0으로 scale down하는 기능이 없음
    • CPU 기반의 autoscaling 제공

설정은 class annotation을 설정해서 타입을 설정 할 수 있습니다.

  • Global Settings Key: pod-autoscaler-class
  • Per-revision annotation key: autoscaling.knative.dev/class
  • Possible values: kpa.autoscaling.knative.dev or hpa.autoscaling.knative.dev
  • Default: kpa.autoscaling.knative.dev

3.2 Configuration

3.2.1 Per Revision Example

spec.template.metadata.annotations 여기에서 autoscaling.knative.dev/class 설정

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-go

3.2.2 Global(ConfigMap)

$ kubectl edit configmap config-autoscaler -n knative-serving 
apiVersion: v1
kind: ConfigMap
metadata:
 name: config-autoscaler
 namespace: knative-serving
data:
 pod-autoscaler-class: "kpa.autoscaling.knative.dev"

3.2.3 Global(Operator)

$ kubectl edit configmap config-autoscaler -n knative-serving 
apiVersion: operator.knative.dev/v1alpha1
kind: KnativeServing
metadata:
  name: knative-serving
spec:
  config:
    autoscaler:
      pod-autoscaler-class: "kpa.autoscaling.knative.dev"

3.3 Example

cat <<EOF > service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: autoscale-go
  namespace: default
spec:
  template:
    metadata:
      annotations:
        # Target 10 in-flight-requests per pod.
        autoscaling.knative.dev/target: "10"
    spec:
      containers:
      - image: gcr.io/knative-samples/autoscale-go:0.1
EOF
$ kubectl apply -f service.yaml
$ kubectl get ksvc autoscale-go
NAME           URL                                       LATESTCREATED        LATESTREADY          READY   REASON
autoscale-go   http://autoscale-go.default.ai.platform   autoscale-go-xdstf   autoscale-go-xdstf   True    
$ INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
$ curl  -H "Host:autoscale-go.default.ai.platform" "${INGRESS_HOST}?sleep=500&prime=10000&bloat=5"
Allocated 5 Mb of memory.
The largest prime less than 10000 is 9973.
Slept for 500.11 milliseconds.

Load Test를 진행합니다.

$ hey -z 30s -c 200 -H "Host: autoscale-go.default.ai.platform" "http://${INGRESS_HOST}?sleep=5000&prime=1000000&bloat=20"
$ kubectl get pods
NAME                                                              READY   STATUS    RESTARTS   AGE
autoscale-go-2fkh2-deployment-65bc9d7764-zq2sr                    2/2     Running   0          5m17s
autoscale-go-5n6gm-deployment-657678f8bd-xp87n                    2/2     Running   0          6m31s
autoscale-go-dm54z-deployment-c48fcb7ff-49ckr                     2/2     Running   0          179m