├── .gitignore ├── 1-setup.sh ├── 2-deploy.sh ├── 3-kube-setup.sh ├── 4-invoke.sh ├── 5-cleanup.sh ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── function ├── lambda_function.py └── requirements.txt └── template.yml /.gitignore: -------------------------------------------------------------------------------- 1 | bucket-name.txt 2 | cluster-name.txt 3 | out.yml 4 | out.json 5 | lambda_build 6 | .DS_Store -------------------------------------------------------------------------------- /1-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! hash aws 2>/dev/null; then 3 | echo "This script requires the AWS cli installed" 4 | exit 2 5 | fi 6 | 7 | BUCKET_ID=$(dd if=/dev/random bs=8 count=1 2>/dev/null | od -An -tx1 | tr -d ' \t\n') 8 | BUCKET_NAME=lambda-artifacts-$BUCKET_ID 9 | echo $BUCKET_NAME > bucket-name.txt 10 | aws s3 mb s3://$BUCKET_NAME 11 | 12 | echo "Enter your cluster name: " 13 | read CLUSTER_NAME 14 | echo $CLUSTER_NAME > cluster-name.txt 15 | -------------------------------------------------------------------------------- /2-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! hash aws 2>/dev/null || ! hash pip3 2>/dev/null; then 3 | echo "This script requires the AWS cli, and pip3 installed" 4 | exit 2 5 | fi 6 | 7 | set -eo pipefail 8 | ARTIFACT_BUCKET=$(cat bucket-name.txt) 9 | CLUSTER_NAME=$(cat cluster-name.txt) 10 | rm -rf lambda_build ; mkdir lambda_build ; cd lambda_build 11 | cp -r ../function/* . 12 | pip3 install --target . -r requirements.txt 13 | cd ../ 14 | aws cloudformation package --template-file template.yml --s3-bucket $ARTIFACT_BUCKET --output-template-file out.yml 15 | aws cloudformation deploy --template-file out.yml \ 16 | --stack-name eks-lambda-python \ 17 | --capabilities CAPABILITY_NAMED_IAM \ 18 | --parameter-overrides ClusterName=$CLUSTER_NAME 19 | -------------------------------------------------------------------------------- /3-kube-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! hash aws 2>/dev/null || ! hash kubectl 2>/dev/null || ! hash eksctl 2>/dev/null; then 3 | echo "This script requires the AWS cli, kubectl, and eksctl installed" 4 | exit 2 5 | fi 6 | 7 | set -eo pipefail 8 | 9 | ROLE_ARN=$(aws cloudformation describe-stacks --stack-name eks-lambda-python --query "Stacks[0].Outputs[?OutputKey=='Role'].OutputValue" --output text) 10 | CLUSTER_NAME=$(cat cluster-name.txt) 11 | RBAC_OBJECT='kind: Role 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | metadata: 14 | name: read-only 15 | namespace: default 16 | rules: 17 | - apiGroups: [""] 18 | resources: ["*"] 19 | verbs: ["get", "watch", "list"] 20 | --- 21 | kind: RoleBinding 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | metadata: 24 | name: read-only-binding 25 | namespace: default 26 | roleRef: 27 | kind: Role 28 | name: read-only 29 | apiGroup: rbac.authorization.k8s.io 30 | subjects: 31 | - kind: Group 32 | name: read-only-group' 33 | 34 | 35 | echo ========== 36 | echo Create Role and RoleBinding in Kubernetes with kubectl 37 | echo ========== 38 | echo "$RBAC_OBJECT" 39 | echo 40 | while true; do 41 | read -p "Do you want to create the Role and RoleBinding? (y/n)" response 42 | case $response in 43 | [Yy]* ) echo "$RBAC_OBJECT" | kubectl apply -f -; break;; 44 | [Nn]* ) break;; 45 | * ) echo "Response must start with y or n.";; 46 | esac 47 | done 48 | 49 | echo 50 | echo ========== 51 | echo Update aws-auth configmap with a new mapping 52 | echo ========== 53 | echo Cluster: $CLUSTER_NAME 54 | echo RoleArn: $ROLE_ARN 55 | echo 56 | while true; do 57 | read -p "Do you want to create the aws-auth configmap entry? (y/n)" response 58 | case $response in 59 | [Yy]* ) eksctl create iamidentitymapping --cluster $CLUSTER_NAME --group read-only-group --arn $ROLE_ARN; break;; 60 | [Nn]* ) break;; 61 | * ) echo "Response must start with y or n.";; 62 | esac 63 | done 64 | 65 | -------------------------------------------------------------------------------- /4-invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! hash aws 2>/dev/null; then 3 | echo "This script requires the AWS cli installed" 4 | exit 2 5 | fi 6 | 7 | set -eo pipefail 8 | 9 | while true; do 10 | aws lambda invoke --function-name lambda-eks-getpods-python --payload '{}' out.json 11 | cat out.json 12 | echo "" 13 | sleep 2 14 | done 15 | -------------------------------------------------------------------------------- /5-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! hash aws 2>/dev/null; then 3 | echo "This script requires the AWS cli installed" 4 | exit 2 5 | fi 6 | 7 | set -eo pipefail 8 | STACK=eks-lambda-python 9 | if [[ $# -eq 1 ]] ; then 10 | STACK=$1 11 | echo "Deleting stack $STACK" 12 | fi 13 | FUNCTION=lambda-eks-getpods-python 14 | aws cloudformation delete-stack --stack-name $STACK 15 | echo "Deleted $STACK stack." 16 | 17 | if [ -f bucket-name.txt ]; then 18 | ARTIFACT_BUCKET=$(cat bucket-name.txt) 19 | if [[ ! $ARTIFACT_BUCKET =~ lambda-artifacts-[a-z0-9]{16} ]] ; then 20 | echo "Bucket was not created by this application. Skipping." 21 | else 22 | while true; do 23 | read -p "Delete deployment artifacts and bucket ($ARTIFACT_BUCKET)? (y/n)" response 24 | case $response in 25 | [Yy]* ) aws s3 rb --force s3://$ARTIFACT_BUCKET; rm bucket-name.txt; break;; 26 | [Nn]* ) break;; 27 | * ) echo "Response must start with y or n.";; 28 | esac 29 | done 30 | fi 31 | fi 32 | 33 | while true; do 34 | read -p "Delete function log group (/aws/lambda/$FUNCTION)? (y/n)" response 35 | case $response in 36 | [Yy]* ) aws logs delete-log-group --log-group-name /aws/lambda/$FUNCTION; break;; 37 | [Nn]* ) break;; 38 | * ) echo "Response must start with y or n.";; 39 | esac 40 | done 41 | 42 | rm -f out.yml out.json function/main 43 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Amazon EKS Kubernetes API from AWS Lambda 2 | 3 | Access the Kubernetes API from a Lambda function entirely in code. This is the source code for the blog article [A Container-Free Way to Configure Kubernetes Using AWS Lambda]( https://aws.amazon.com/blogs/opensource/a-container-free-way-to-configure-kubernetes-using-aws-lambda/). 4 | 5 | ## Security 6 | 7 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 8 | 9 | ## License 10 | 11 | This library is licensed under the MIT-0 License. See the LICENSE file. 12 | 13 | -------------------------------------------------------------------------------- /function/lambda_function.py: -------------------------------------------------------------------------------- 1 | "Lambda function list pods in EKS cluster" 2 | import base64 3 | import os 4 | import logging 5 | import re 6 | import boto3 7 | 8 | from botocore.signers import RequestSigner 9 | from kubernetes import client, config 10 | 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | STS_TOKEN_EXPIRES_IN = 60 16 | session = boto3.session.Session() 17 | sts = session.client('sts') 18 | service_id = sts.meta.service_model.service_id 19 | cluster_name = os.environ["CLUSTER_NAME"] 20 | eks = boto3.client('eks') 21 | cluster_cache = {} 22 | 23 | def get_cluster_info(): 24 | "Retrieve cluster endpoint and certificate" 25 | cluster_info = eks.describe_cluster(name=cluster_name) 26 | endpoint = cluster_info['cluster']['endpoint'] 27 | cert_authority = cluster_info['cluster']['certificateAuthority']['data'] 28 | cluster_info = { 29 | "endpoint" : endpoint, 30 | "ca" : cert_authority 31 | } 32 | return cluster_info 33 | 34 | def get_bearer_token(): 35 | "Create authentication token" 36 | signer = RequestSigner( 37 | service_id, 38 | session.region_name, 39 | 'sts', 40 | 'v4', 41 | session.get_credentials(), 42 | session.events 43 | ) 44 | 45 | params = { 46 | 'method': 'GET', 47 | 'url': 'https://sts.{}.amazonaws.com/' 48 | '?Action=GetCallerIdentity&Version=2011-06-15'.format(session.region_name), 49 | 'body': {}, 50 | 'headers': { 51 | 'x-k8s-aws-id': cluster_name 52 | }, 53 | 'context': {} 54 | } 55 | 56 | signed_url = signer.generate_presigned_url( 57 | params, 58 | region_name=session.region_name, 59 | expires_in=STS_TOKEN_EXPIRES_IN, 60 | operation_name='' 61 | ) 62 | base64_url = base64.urlsafe_b64encode(signed_url.encode('utf-8')).decode('utf-8') 63 | 64 | # remove any base64 encoding padding: 65 | return 'k8s-aws-v1.' + re.sub(r'=*', '', base64_url) 66 | 67 | 68 | def lambda_handler(_event, _context): 69 | "Lambda handler" 70 | if cluster_name in cluster_cache: 71 | cluster = cluster_cache[cluster_name] 72 | else: 73 | # not present in cache retrieve cluster info from EKS service 74 | cluster = get_cluster_info() 75 | # store in cache for execution environment resuse 76 | cluster_cache[cluster_name] = cluster 77 | 78 | kubeconfig = { 79 | 'apiVersion': 'v1', 80 | 'clusters': [{ 81 | 'name': 'cluster1', 82 | 'cluster': { 83 | 'certificate-authority-data': cluster["ca"], 84 | 'server': cluster["endpoint"]} 85 | }], 86 | 'contexts': [{'name': 'context1', 'context': {'cluster': 'cluster1', "user": "user1"}}], 87 | 'current-context': 'context1', 88 | 'kind': 'Config', 89 | 'preferences': {}, 90 | 'users': [{'name': 'user1', "user" : {'token': get_bearer_token()}}] 91 | } 92 | 93 | config.load_kube_config_from_dict(config_dict=kubeconfig) 94 | v1_api = client.CoreV1Api() # api_client 95 | ret = v1_api.list_namespaced_pod("default") 96 | return f"There are {len(ret.items)} pods in the default namespace." 97 | 98 | print(lambda_handler(None, None)) 99 | -------------------------------------------------------------------------------- /function/requirements.txt: -------------------------------------------------------------------------------- 1 | kubernetes~=24.2.0 -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: An AWS Lambda application that calls the EKS Kubernetes API. 4 | 5 | Parameters: 6 | ClusterName: 7 | Description: Name of the EKS cluster to monitor 8 | Type: String 9 | 10 | Resources: 11 | EksGetPodsFunction: 12 | Type: AWS::Serverless::Function 13 | Properties: 14 | FunctionName: lambda-eks-getpods-python 15 | Environment: 16 | Variables: 17 | CLUSTER_NAME: !Ref ClusterName 18 | Handler: lambda_function.lambda_handler 19 | Runtime: python3.9 20 | CodeUri: lambda_build/. 21 | Description: Call the AWS Lambda API 22 | Role: !GetAtt 'EksGetPodsFunctionRole.Arn' 23 | ReservedConcurrentExecutions: 5 24 | Timeout: 30 25 | MemorySize: 256 26 | 27 | 28 | EksGetPodsFunctionRole: 29 | Type: AWS::IAM::Role 30 | Properties: 31 | AssumeRolePolicyDocument: 32 | Version: '2012-10-17' 33 | Statement: 34 | - Effect: Allow 35 | Principal: 36 | Service: 37 | - lambda.amazonaws.com 38 | Action: 39 | - sts:AssumeRole 40 | Path: / 41 | ManagedPolicyArns: 42 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 43 | Policies: 44 | - PolicyName: root 45 | PolicyDocument: 46 | Version: '2012-10-17' 47 | Statement: 48 | - Effect: Allow 49 | Action: 50 | - eks:DescribeCluster 51 | Resource: !Sub 'arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}' 52 | 53 | 54 | Outputs: 55 | Role: 56 | Description: IAM Role 57 | Value: !GetAtt 'EksGetPodsFunctionRole.Arn' 58 | 59 | 60 | --------------------------------------------------------------------------------