Knative
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: NodePort
를 type: 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
- Knative Pod Autoscaler (KPA)
- Knative Serving Core의 한 부분이며, 디폴트값이 사용이다.
- 트래픽이 없을경우 0으로 scale 하는 기능도 있음 (scale down)
- CPU 기반의 autoscaling은 지원하지 않음
- 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
orhpa.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