亚马逊AWS官方博客

在 Amazon EKS 集群中使用 Envoy 无缝集成 AWS Lambda

什么是Envoy

Envoy 是Lyft公司贡献给CNCF的高性能L4/L7代理和通信总线开源项目, 目前流行的service mesh 产品/服务如istio, AWS App Mesh 均使用envoy作为数据平面。在最新发布版本Envoy 1.14 中官方内置了AWS Lambda filter, 通过该filter可以将AWS Lambda 直接暴露成kubernetes的服务供其他服务调用。 本文将演示在AWS EKS 中如何使用envoy 1.14集成AWS Lambda。

场景

越来越多的客户选择Amazon EKS作为首选的微服务平台,通过kubernetes配置文件将自己的应用部署到Amazon EKS上。在微服务架构设计中客户往往会考虑使用serverless架构即AWS Lambda来扩展自己的服务. 在Amazon EKS集群中我们可以通过2种方式来使用AWS Lambda服务:

  1. 通过集群外的Amazon API Gateway,或者Application Load Balancer来访问AWS Lambda,对于这些API Gateway或者ALB客户需要进行额外的管理、维护。
  2. 在docker image 中编写代码通过SDK来访问AWS Lambda,这种属于代码级别的调用,往往缺少可配置的灵活性。

 

架构图(未使用envoy):

为了降低上述方案的集成难度、管理复杂度,作为service mesh首选的数据平面Envoy在1.14中推出了AWS Lambda filter, 为此新的架构图如下, 可以看到我们不再对Amazon EKS集群外的Amazon API Gateway/ALB等资源进行管理,也不需要在代码中直接调用AWS Lambda SDK, 仅仅通过标准的Envoy的配置文件就可以将任意AWS Lambda暴露成Amazon EKS集群中的服务,与其他服务在使用上并无差异。

 

 

架构图:

前提条件

AWS 账号, aws cli 命令行工具,  使用eksctl已经创建好的EKS集群。

部署示例lambda

创建lambda_function.py

cat <<EOF >>lambda_function.py
import json
import base64

def lambda_handler(event, context):
    # TODO implement
    body=""
    if "body" in event:
        body=event["body"]
    if "is_base64_encoded" in event and event["is_base64_encoded"]:
        body=str(base64.decodebytes(bytes(body,"utf-8")))
        body="body is {}".format(body)
    return {
        'statusCode': 200,
        'body': json.dumps("hello envoy with lambda. {}".format(body))
    }
EOF

创建IAM role

cat <<EOF >>trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

#设置默认region为us-east-1
export AWS_DEFAULT_REGION=us-east-1
#创建lambda所需要的role和权限
aws iam create-role --role-name hello-envoy-lambda --assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy --role-name hello-envoy-lambda --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

部署lambda

#打包python文件
zip function.zip lambda_function.py

#部署lambda
LAMBDA_ROLE=$(aws iam get-role --role-name hello-envoy-lambda --query "Role.Arn" --out text)

aws lambda create-function --function-name hello-envoy-lambda \
--zip-file fileb://function.zip \
--runtime python3.7 \
--handler lambda_function.lambda_handler \
--role ${LAMBDA_ROLE}

部署hello-lambda-envoy应用到EKS集群

使用eksctl创建kubernetes service account

需要为hello-lambda-envoy 应用创建有lambda 执行权限的kubernetes service account,我们使用eksctl 工具来完成service account的创建。

#export CLUSTER_NAME=<你的eks集群名字>
export CLUSTER_NAME=eksworkshop

eksctl create iamserviceaccount --name hello-envoy-lambda --namespace default \
    --cluster ${CLUSTER_NAME} --attach-policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \
    --approve --override-existing-serviceaccounts 

#确认hello-envoy-lambda是否有lambda执行role
kubectl get serviceaccount hello-envoy-lambda -o yaml

参考输出

配置config map

envoy.yaml是envoy的配置文件,我们将文件发布成kubernets 的config map ,以供deployment/pod 使用,在文件中已经配置好了官方的aws lambda filter , 只需要替换lambda 对应的arn即可。

创建配置模版envoy.yaml.template

cat <<EOF >> envoy.yaml.template
admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address:
      protocol: TCP
      address: 127.0.0.1
      port_value: 9901
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
            - name: envoy.file_access_log
              config:
                 path: "/dev/stdout"
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: service_bing
                request_headers_to_add:
                   - header:
                        key: X-IWant-Host
                        value: "lambda.us-east-1.amazonaws.com"
          http_filters:
          - name: envoy.filters.http.aws_lambda
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.aws_lambda.v3.Config
              arn: "<LAMBDA_ARN>"
              payload_passthrough: false
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: service_bing
    connect_timeout: 10s
    type: LOGICAL_DNS
    # Comment out the following line to test on v6 networks
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    metadata:
      filter_metadata:
        com.amazonaws.lambda:
           egress_gateway: true
    load_assignment:
      cluster_name: service_bing
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: lambda.us-east-1.amazonaws.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: "*.amazonaws.com"
EOF

使用envoy.yaml.template 生成envoy配置文件

#生成Envoy配置文件envoy.yaml
LAMBDA_ARN=$(aws lambda get-function --function-name hello-envoy-lambda --query "Configuration.FunctionArn" --output text)

#使用sed替换模版中<LAMBDA_ARN>为正确的AWS Lambda ARN
sed -e "s/<LAMBDA_ARN>/${LAMBDA_ARN}/g" envoy.yaml.template >  envoy.yaml

#使用envoy.yaml创建config map
kubectl create configmap config-envoy-lambda --from-file=envoy.yaml

部署hello-lambda-envoy应用

创建kubernetes 部署文件 envoy-lambda-eks.yaml

cat <<EOF >>envoy-lambda-eks.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: envoy-lambda
  labels:
    app: envoy-lambda
spec:
  replicas: 1
  selector:
    matchLabels:
      app: envoy-lambda
  template:
    metadata:
      labels:
        app: envoy-lambda
    spec:
      serviceAccountName: hello-envoy-lambda
      containers:
      - name: envoy-lambda
        image: envoyproxy/envoy:v1.14.1
        ports:
        - containerPort: 10000
        volumeMounts:
        - name: config-envoy-lambda
          mountPath: /etc/envoy
      volumes:
      - name: config-envoy-lambda
        configMap:
          name: config-envoy-lambda
---
apiVersion: v1
kind: Service
metadata:
  name: "service-envoy-lambda"
  annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
  selector:
    app: envoy-lambda
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 10000
    targetPort: 10000
EOF

#部署
kubectl apply -f envoy-lambda-eks.yaml

#验证
kubectl get deploy envoy-lambda
kubectl get svc  service-envoy-lambda

参考输出

使用curl调用部署在EKS里面的lambda服务.

#创建测试data.json
cat <<EOF >>data.json
{"data":"this is test!"}
EOF

#获取NLB的地址,端口固定为10000
ENVOY_ENDPOINT=$(kubectl get svc service-envoy-lambda --output jsonpath='{.status.loadBalancer.ingress[0].hostname}'):10000

curl -v -X POST -H "Content-Type: application/json"  -H "Host:lambda" -d @data.json ${ENVOY_ENDPOINT}

参考输出

 清除资源

#清除kubernetes 部署
kubectl delete -f envoy-lambda-eks.yaml
kubectl delete configmap config-envoy-lambda

#删除iamserviceaccount
eksctl delete  iamserviceaccount --name hello-envoy-lambda --namespace default --cluster ${CLUSTER_NAME}

#删除lambda以及role 
aws lambda delete-function --function-name hello-envoy-lambda
aws iam detach-role-policy  --role-name hello-envoy-lambda --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name hello-envoy-lambda

总结

Envoy 官方最新版本1.14已经提供了的AWS Lambda filter,可以帮助用户更快得在kubernetes集群环境下集成AWS Lambda,通过这个版本我们可以更方便的在EKS,AWS App Mesh环境下使用AWS Lambda。

 

参考:

Envoy 1.14版本说明:https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.14.0

 

本篇作者

粟伟

AWS解决方案架构师,开源项目爱好者,致力于云原生应用推广、落地。具有15年以上的信息技术行业专业经验,担任过高级软件工程师,系统架构师等职位,在加入AWS之前曾就职于Bea, Oracle, IBM等公司。