Deploy your first FaaS Application on Knative

2020年12月26日


In GCP, create a new service account and a service account key for use with Container Registry repositories only.

Create the service account for interacting with repositories. Replace SvcAccName with a name for the service account.
$ gcloud iam service-accounts create SvcAccName

Created service account [SvcAccName].

Grant permissions to the service account. Replace [ProjectId] with your project ID and ROLE with the appropriate role for the service account.
$ gcloud projects add-iam-policy-binding [ProjectId] --member "serviceAccount:SvcAccName@###.iam.gserviceaccount.com" --role "roles/viewer"
Updated IAM policy for project [###].
bindings:
...
- members:
  - serviceAccount:SvcAccName@###.iam.gserviceaccount.com
  role: roles/viewer
etag: BwW39TyIHMo=
version: 1

Generate the key file. In this example, the output key file name is keyfile.json.
$ gcloud iam service-accounts keys create keyfile.json --iam-account SvcAccName@[ProjectId].iam.gserviceaccount.com
created key [1291###6612] of type [json] as [keyfile.json] for [SvcAccName@###.iam.gserviceaccount.com]

Use the service account key as your password to authenticate with Docker.
$ cat keyfile.json | docker login -u _json_key --password-stdin https://gcr.io
Login Succeeded
Docker is now authenticated with Container Registry.

kubectl create secret docker-registry gcr-json-key \
  --docker-server=https://gcr.io \
  --docker-username=_json_key \
  --docker-password="$(cat ./keyfile.json)"
$ kubectl create secret docker-registry gcr-json-key \
> --docker-server=https://gcr.io \
> --docker-username=_json_key \
> --docker-password="$(cat ./keyfile.json)"
secret/gcr-json-key created

$ kubectl get secret
NAME TYPE DATA AGE
...
gcr-json-key kubernetes.io/dockerconfigjson 1 45d
...



spec:
  ...
  template:
    ...
    spec:
      imagePullSecrets:
      - name: gcr-json-key
      containers:
      - image: ...
...

Clone the repo into a new directory. In this example, we name the new directory as "blog-knative".
$ git clone -b master git@github.com:###/###.git blog-knative
Cloning into 'blog-knative'...
remote: Enumerating objects: 35, done.
remote: Counting objects: 100% (35/35), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 35 (delta 10), reused 30 (delta 5), pack-reused 0
Receiving objects: 100% (35/35), 13.50 KiB | 13.50 MiB/s, done.
Resolving deltas: 100% (10/10), done.

$ cd blog-knative/

The example "MyTableName" represents the DynamoDB table name. The "MyIndexName" represents the index of this DynamoDB table.
$ cat blog-knative/env_test.sh
#!/bin/bash
# To get var persist after this script has completed:
# source ./env_test.sh
export GCPPROJECT=###
export REPO="gcr.io/${GCPPROJECT}"
export TAG="${REPO}/knative-pk2hash" IMAGE=$TAG
export TableName=MyTableName
export IndexName=MyIndexName

Use your existing GCP project ID. Replace <GCP-Project-ID> with your GCP project ID.
export GCPPROJECT=<GCP-Project-ID>
export REPO="gcr.io/${GCPPROJECT}"
export TAG="${REPO}/knative-pk2hash" IMAGE=$TAG
Replace <DynamoDBTableName> with your DynamoDB table name.
export TableName=<DynamoDBTableName>
Replace <DynamoDBIndexName> with your DynamoDB table's index name.
export IndexName=<DynamoDBIndexName>

$ source blog-knative/env_test.sh


Prebake an container image for futher shared use. File "DockerfilePrebakeImg":
FROM golang

WORKDIR /app

RUN go get github.com/aws/aws-lambda-go/events
RUN go get github.com/aws/aws-lambda-go/lambda
RUN go get github.com/aws/aws-sdk-go/aws
RUN go get github.com/aws/aws-sdk-go/aws/awserr
RUN go get github.com/aws/aws-sdk-go/aws/session
RUN go get github.com/aws/aws-sdk-go/service/dynamodb
RUN go get github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute
RUN go get github.com/gorilla/mux
PS:
Below WORKDIR instruction makes sure when the container runs, inside the container, the current directory is "/app/". If the specific directory does not exist in the original image, the WORKDIR will create this new directory automatically.
...
WORKDIR /app
...
The packages will be installed to directory "/go/src/github.com/".
...
RUN go get github.com/aws/aws-lambda-go/events
RUN go get github.com/aws/aws-lambda-go/lambda
RUN go get github.com/aws/aws-sdk-go/aws
RUN go get github.com/aws/aws-sdk-go/aws/awserr
RUN go get github.com/aws/aws-sdk-go/aws/session
RUN go get github.com/aws/aws-sdk-go/service/dynamodb
RUN go get github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute
RUN go get github.com/gorilla/mux
...

$ docker build --tag "${REPO}/prebake" --file DockerfilePrebakeImg .
Sending build context to Docker daemon  97.28kB
Step 1/10 : FROM golang
 ---> 5f9d35ce5cfe
Step 2/10 : WORKDIR /app
 ---> Using cache
 ---> 64e0befeef58
Step 3/10 : RUN go get github.com/aws/aws-lambda-go/events
 ---> Using cache
 ---> dfb44cbcd7e7
Step 4/10 : RUN go get github.com/aws/aws-lambda-go/lambda
 ---> Using cache
 ---> c7cf0bdb4c79
Step 5/10 : RUN go get github.com/aws/aws-sdk-go/aws
 ---> Using cache
 ---> 9de8d250af45
Step 6/10 : RUN go get github.com/aws/aws-sdk-go/aws/awserr
 ---> Using cache
 ---> 68b5a9290717
Step 7/10 : RUN go get github.com/aws/aws-sdk-go/aws/session
 ---> Using cache
 ---> c28beb73e648
Step 8/10 : RUN go get github.com/aws/aws-sdk-go/service/dynamodb
 ---> Using cache
 ---> 86aeb0da3aa8
Step 9/10 : RUN go get github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute
 ---> Using cache
 ---> cd413d583eef
Step 10/10 : RUN go get github.com/gorilla/mux
 ---> Using cache
 ---> 92bd4f601663
Successfully built 92bd4f601663
Successfully tagged gcr.io/[GCP-Project-ID]/prebake:latest

$ docker push "${REPO}/prebake"
Using default tag: latest
The push refers to repository [gcr.io/[GCP-Project-ID]/prebake]
fa03def44984: Pushed
8e8b467b9722: Pushed
3fc1d702cf24: Pushed
65c457f81b03: Pushed
d895236ab75c: Pushed
cb585322a308: Pushed
6f498ce811ae: Pushed
15d4d54598fb: Pushed
73465b7c2cdd: Pushed
86eac4a15979: Layer already exists
4e68eae3a5f4: Layer already exists
7857d5f3d252: Layer already exists
c5f4367d4a59: Layer already exists
ceecb62b2fcc: Layer already exists
193bc1d68b80: Layer already exists
f0e10b20de19: Layer already exists
latest: digest: sha256:36f0e20f2145ead31cd7dc26e14cc68cd88cc12f34b1fa8ab6c008ccee46b397 size: 3686

Dockerfile:
FROM gcr.io/[ProjectId]/prebake AS builder

COPY ./pk2hash/src /app/

# Below build task will generate a file called "main" in the "/app/" directory
# inside the container image.
RUN CGO_ENABLED=0 go build -o main .

FROM gcr.io/distroless/base

COPY --from=builder /app/main /sample

ENTRYPOINT ["/sample"]

$ docker build --tag $TAG --file pk2hash/Dockerfile .
Sending build context to Docker daemon  239.6kB
Step 1/6 : FROM gcr.io/[GCP-Project-ID]/prebake AS builder
 ---> 04793abeda00
Step 2/6 : COPY ./src /app/
 ---> 98d0c08f37f7
Step 3/6 : RUN CGO_ENABLED=0 go build -o main .
 ---> Running in b3136db4af3d
Removing intermediate container b3136db4af3d
 ---> 0a9dadbfc624
Step 4/6 : FROM gcr.io/distroless/base
 ---> 5cda88510683
Step 5/6 : COPY --from=builder /app/main /sample
 ---> Using cache
 ---> 562526b3d327
Step 6/6 : ENTRYPOINT ["/sample"]
 ---> Using cache
 ---> bdc5c0356d23
Successfully built bdc5c0356d23
Successfully tagged gcr.io/[GCP-Project-ID]/knative-pk2hash:test

When checking the content of this directory (e.g. access it via: docker run -it "${REPO}/rest-api-go"), we could observe that files are copied from the outside directory (at which directory that Dockerfile exist) to the inside container directory, i.e. /app/.
root@###:/app# ls -lah /app/
total 7.3M
drwxr-xr-x  1 root root 4.0K Dec 28 23:23 .
drwxr-xr-x  1 root root 4.0K Dec 28 14:01 ..
drwxr-xr-x  8 root root 4.0K Dec 28 23:20 .git
drwxr-xr-x  3 root root 4.0K Dec 28 23:20 .github
-rw-r--r--  1 root root   35 Dec 28 23:20 .gitignore
-rw-r--r--  1 root root  19K Dec 28 23:20 CONTRIBUTING.md
-rw-r--r--  1 root root  15K Dec 28 23:20 Gopkg.lock
-rw-r--r--  1 root root  433 Dec 28 23:20 Gopkg.toml
-rw-r--r--  1 root root  30K Dec 28 23:20 LICENSE
-rw-r--r--  1 root root  161 Dec 28 23:20 OWNERS
-rw-r--r--  1 root root 1.1K Dec 28 23:20 OWNERS_ALIASES
-rw-r--r--  1 root root 3.5K Dec 28 23:20 README.md
-rw-r--r--  1 root root 5.1K Dec 28 23:20 _index.html
-rw-r--r--  1 root root  21K Dec 28 23:20 background.png
drwxr-xr-x  7 root root 4.0K Dec 28 23:20 blog
drwxr-xr-x  6 root root 4.0K Dec 28 23:20 community
-rw-r--r--  1 root root  838 Dec 28 23:20 doc-releases.md
drwxr-xr-x  9 root root 4.0K Dec 28 23:20 docs
drwxr-xr-x  2 root root 4.0K Dec 28 23:20 hack
-rw-r--r--  1 root root 3.2K Dec 28 23:20 new-page-template.md
-rwxr-xr-x  1 root root 7.2M Dec 28 23:23 rest-api-go
-rw-r--r--  1 root root   45 Dec 28 23:20 search.md
-rw-r--r--  1 root root 2.9K Dec 28 23:20 smoketest.md
drwxr-xr-x  6 root root 4.0K Dec 28 23:20 test
drwxr-xr-x 10 root root 4.0K Dec 28 23:20 vendor

Below command in the Dockerfile is will build out a new file called "main" under the directory "/app/".
RUN CGO_ENABLED=0 go build -o main .

Push the container image to a container registry.
$ docker push $TAG
The push refers to repository [gcr.io/[GCP-Project-ID]/knative-pk2hash]
1ef81aba1286: Pushed 
e2db5f1bf240: Layer already exists 
7a5b9c0b4b14: Layer already exists 
latest: digest: sha256:8a1e8429b1b2263994a0039ea0b280d27f3ff7eed7b70d9914a686558ca918bc size: 949

$ cat sample-template.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: pk2hash
  namespace: default
spec:
  template:
    metadata:
      name: pk2hash-first
      annotations:
        sidecar.istio.io/rewriteAppHTTPProbers: "true"
    spec:
      containers:
      - image: ${IMAGE}
        imagePullPolicy: Always
        env:
          - name: TableName
            value: ${TableName}
          - name: IndexName
            value: ${IndexName}
        readinessProbe:
          httpGet:
            path: /healthcheck
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /healthcheck
          initialDelaySeconds: 5
          periodSeconds: 30
          timeoutSeconds: 10
          failureThreshold: 3
Execute below command, which will replace the image name, table name and index name based on the environment variables.
$ envsubst < pk2hash/sample-template.yaml > pk2hash/pkhash.yaml

On the machine running kubectl client, issue below command.
Edit aws-auth ConfigMap. Replace <user> with your IAM user name. Use "aws sts get-caller-identity" command to get the current IAM user's ARN.
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    ...
  mapUsers: |
    - userarn: arn:aws:iam::123456789012:user/<user>
      username: <user>
      groups:
        - system:masters

$ kubectl apply -f pk2hash/pkhash.yaml
service.serving.knative.dev/### created

$ watch kubectl get pod

NB
Make sure the frontend application code is calls URL: ###.default.tianzhui.cloud

Test:
test/test_test.sh:
#!/bin/bash
echo "Verifying Health Check API... (no news is good news)"
curl "http://pk2hash-test.default.tianzhui.cloud/healthcheck"
echo "Testing API..."
echo "Test result:"
curl "http://pk2hash-test.default.tianzhui.cloud/prod/hasher?querystring=1##9,3##1"
echo ""
echo "Test completed."

$ ./pk2hash/test/test_test.sh
Verifying Health Check API... (no news is good news)
Testing API...
Test result:
{"id_results":{"10kk4jvq":"1##9","15v2dcnv":"3##1"}}
Test completed.

Tear down the test environment.
$ kubectl delete -f pk2hash/pkhash.yaml
service.serving.knative.dev "pk2hash-test" deleted

env_prod.sh:
#!/bin/bash
# To get var persist after this script has completed:
# source ./env_prod.sh
export GCPPROJECT=ProjectId
export REPO="gcr.io/${GCPPROJECT}"
export ENV="prod"
export TAG="${REPO}/knative-pk2hash:${ENV}"
export IMAGE=$TAG
export TableName=MyTableName
export IndexName=MyIndexName

source pk2hash/env_prod.sh

$ docker tag gcr.io/[ProjectId]/knative-pk2hash:test gcr.io/[ProjectId]/knative-pk2hash:prod

Push the image to the "prod" tag.
docker push $TAG
The push refers to repository [gcr.io/[ProjectId]/knative-pk2hash]
e###3: Layer already exists 
e###0: Layer already exists 
7###4: Layer already exists 
prod: digest: sha256:5###b size: 949

envsubst < pk2hash/sample-template.yaml > pk2hash/pkhash.yaml

Apply the configuration to the production environment.
kubectl apply -f pk2hash/pkhash.yaml

Test:
test/test_prod.sh:
#!/bin/bash
echo "Verifying Health Check API... (no news is good news)"
curl "http://pk2hash-prod.default.tianzhui.cloud/healthcheck"
echo "Testing API..."
echo "Test result:"
curl "http://pk2hash-prod.default.tianzhui.cloud/prod/hasher?querystring=1##9,3##1"
echo ""
echo "Test completed."

$ ./pk2hash/test/test_prod.sh
Verifying Health Check API... (no news is good news)
Testing API...
Test result:
{"id_results":{"10kk4jvq":"1##9","15v2dcnv":"3##1"}}
Test completed.

$ kubectl get ksvc --output=custom-columns=NAME:.metadata.name,URL:.status.url
NAME      URL
pk2hash   http://pk2hash.default.tianzhui.cloud

$ curl http://pk2hash.default.tianzhui.cloud/prod/hasher?querystring=1##9,3##1
{"id_results":{"1###q":"1##9","1###v":"3##1"}}


References

How to install & setup Gettext

Knative code samples

Knative Serving code samples

Creating a RESTful Service - Go

knative / docs release 0.15

How do I resolve an unauthorized server error when I connect to the Amazon EKS API server?

JSON key file

Using Google Container Registry with Kubernetes
-

Category: container Tags: public

Upvote


Downvote