Skip to content

Instantly share code, notes, and snippets.

@weibeld
Last active December 8, 2018 00:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weibeld/5a8d810d740fa8911ad6c1ebb664325f to your computer and use it in GitHub Desktop.
Save weibeld/5a8d810d740fa8911ad6c1ebb664325f to your computer and use it in GitHub Desktop.

2018-12-07

During the last days, figured out a couple of things.

Creating and deleting users and vhosts through the RabbitMQ Management REST API

This is done through simple HTTP calls to different endpoints under http://host:15672/api/. All the calls use HTTP Basic Authentication with username and password of a RabbitMQ user with appropriate permissions.

To create a user and vhost and grant the user access to this vhost only:

  1. Create user (without any tags)
  2. Create vhost
  3. Create permission linking user with vhost

This will be used in the post-signup Lambda function (implemented in Go with the net/http package).

How to use the AWS SDK for Go (aws-sdk-go)

  1. Create a session with common configuration information (region, credentials, profile)
  2. Ceate a service client for the desired AWS service (there's a Go package for every supported AWS service)
  3. Make requests to the AWS service with the service client.

Every AWS service is in fact exposed as an HTTP API and the service client makes requests to this API. There is a documentation of the APIs of all services, for example here for AWS Secrets Manager. An overview with links to the documentation of all services can be found here.

The AWS Console (website) and AWS CLI also carry out their operations by making requests to these APIs. It is not recommended to use the HTTP of an AWS service directly (e.g. with curl). Instead, always use either an AWS SDK, the AWS CLI, or the AWS Console as these implementations handle many low-level details.

Create, list, and read secrets from AWS Secrets Manager with the AWS SDK for Go

Pretty straighforward once it is clear how the AWS SDK for Go works.

This will be needed to read the credentials of the RabbitMQ admin user from the post-signup Lambda function.

Read Kubernetes API objects from YAML files for the Kubernetes Go Client

The Kubernetes Go client (client-go) doesn't provide any easy means for this. The best solution seems to be to use the github.com/ghodss/yaml package to read YAML into a client-go API object struct. This works pretty well:

import (
  "io/ioutil"
  "github.com/ghodss/yaml"
  "k8s.io/api/apps/v1"
)

func main() {
  bytes, err := ioutil.ReadFile("deployment.yml")
  if err != nil {
    panic(err.Error())
  }

  var spec v1.Deployment
  err = yaml.Unmarshal(bytes, &spec)
  if err != nil {
    panic(err.Error())
  }
}

This can also be used in the post-signup Lambda function so that we can define the backend deployment object in a file rather than as a struct in the Go code.

Btw, if the API objects are defined in JSON files, they can be read even easier with standar library means, see here.

Tying things together (cluster creation, RabbitMQ setup, post-signup Lambda function)

  1. Create Kubernetes cluster
    • Apply CloudFormation template plus some additional setup
  2. Deploy and set up RabbitMQ
    • Deploy RabbitMQ deployment and service to cluster
    • Replace default admin user (guest/guest) with a new one with a safe password
    • Store credentials of new admin user in AWS Secrets Manager
    • Store URL of RabbitMQ service somewhere (TODO: where?)
      • Needs to be retrieved by frontend and post-signup Lambda function
  3. (Deploy frontend)
  4. In post-signup Lambda function:
    • Retrieve RabbitMQ service URL
    • Retrieve RabbitMQ admin credentials from AWS Secrets Manager
    • Create a new user and vhost on RabbitMQ (granting user access to only this vhost)
    • Create a backend deployment in the cluster and link with this vhost
    • Store credentials for this RabbitMQ user (username, password, vhost) somewhere (TODO: where?)
      • These need to be retrieved by the frontend when connecting to the backend (together with the RabbitMQ service URL)

Where to store secrets?

Currently, secrets like the Telegram API ID and Hash are saved as Kubernetes secrets in the cluster. Should they also be saved in AWS Secrets Manager so that all the secrets are together?

Where to store user data?

List of tings that have to be saved for each human user:

  • Account username and password (saved in Amazon Cognito)
  • RabbitMQ username, password, vhost
  • Application data (it's probably better to save this persistently for the case of a pod crash)
    • Telethon session file
    • Telegram phone number
    • Lists of peers, patterns, and notification emails

2018-11-28

Figured out how to use the Kubernetes Go client library (client-go).

Created example usage programs in this repository.

2018-11-27

Figured out how to write an AWS Lambda handler function in Go that can execute the aws-iam-authenticator.

Results in this repository.

2018-11-22

Go cross-compiling

It's possible to compile Go programs for other platforms. Different combinations between target operating systems and target architectures are possible.

Target OS and architecture are specified by the following Go env variables:

  • GOOS
  • GOARCH

When you execute go build, then the code is compiled for the target platform that is currently specified by these variables.

Possible combinations of these two variables: table

AWS IAM Authenticator

The AWS IAM Authenticator is a Go program. The binaries compiled for macOS don't run on Linux, and vice versa (cannot execute binary file error).

Thus, if the aws-iam-authenticator binary from macOS is copied to an EC2 instance or a Lambda function, it cannot be executed.

However, you can compile the AWS IAM Authenticator on macOS for the Linux target platform. To this end, set these Go env variables as follows:

  • GOOS=linux
  • GOARCH=amd64

Actually, GOARCH=amd64 is the default for macOS, so it's only necessary to set GOOS.

So, you can build an aws-iam-authenticator binary that will run on an EC2 instance or in a Lambda function with the following command:

GOOS=linux go build github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator

AWS Lambda: effect of code size on duration

The aws-iam-authenticator binary is approximately 24 MB. The zip bundle of a Node.js Lambda function containing the aws-iam-authenticator binary that is uploaded to S3 is approximately 9 MB.

The Lambda function executes aws-iam-authenticator token -i foo.

For the first execution, the duration is 2000 ms, for the subsequent executions, the duration is 750 ms.

These values seem to be constant and not depending on the size of the zip bundle containing the Lambda function code. For example, I artificially increased the size of the bundle to 40 MB, and the durations were the same.

It's possible to reduce the size of the aws-iam-authenticator binary from 24 MB to 18 MB by setting some linker flags:

GOOS=linux go build -ldflags "-w -s" github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator

However, I didn't notice any difference in the function duration.

TGSearch post signup Lambda function

The duration of this function, that is triggered by Cognito after a signup confirmation, is limited ot 5 seconds. So, if the aws-iam-authenticator binary already takes 2 seconds to execute, the available time might become tight.

However, I need to do the same experiment with a Go Lambda function, because we anyway have to use a Go Lambda function and use the Go Kubernetes client library, because the JavaScript Kubernetes client library does not yet support the exec section in the kubeconfig file (credentials plugin).

2018-11-13

Amazon EKS cluster authentication

The Amazon EKS API server uses AWS IAM to authenticate requests from clients, for example, from kubectl. This is described in the docs under Managing Cluster Authentication.

In effect this means that kubectl has to pass an IAM identity to the API server with every request. The API server then verifies this IAM identity, and then grants or denies the requested action to the client.

For submitting an IAM identity, kubectl depends on the AWS IAM Authenticator. This program must be installed on the local system when using kubectl with an EKS cluster (installation instructions for the AWS IAM Authenticator are here, but the easiest way to install it is with go get -u -v github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator).

Kubernetes external credential providers feature

This feature allows a Kubernetes client to execute an external command to get credentials for authenticating to the API server. Amazon EKS relies on this feature, and the external command that produces credentials is the AWS IAM Authenticator.

kubectl must be set up to use the AWS IAM Authenticator as an external credentials provider. How to do this is described on the GitHub page of the AWS IAM Authenticator and in the AWS docs under Create a kubeconfig for Amazon EKS.

In short, it is done by configuring the user for the target cluster in the kubeconfig file as follows:

users:
- name: arn:aws:eks:eu-west-1:202449302273:cluster/test-cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: aws-iam-authenticator
      args:
        - token
        - -i
        - test-cluster
      env: null

The crucial point is the exec section. This section allows to define an external command that is executed to get user credentials that are used to authenticate to the API server. In our case, this external command is aws-iam-authenticator.

If you locally execute aws-iam-authenticator token -i test-cluster, you see the token that it produces for kubectl for authenticating to the cluster.

The exec section is documented in the Kubernetes documentation.

Which Kubernetes client libraries do support it?

Currently, only a few Kubernetes clients support the external credentials provider feature:

The Go client supports it since v1.10. Since kubectl uses the Go client, it also supports it since this time. The code for the "exec plugin" of the Go client seems to be in the plugin/pkg/client/auth/exec directory.

The relevant code in the Python client that uses the "exec plugin" is in the kube_config.py file.

What to do?

Amazon EKS requires to use the external credential provider feature, but only few clients support it. So we probably have to use either the Go client or the Python client.

A complete solution that accesses EKS from AWS Lambda functions is described here. This solution uses the Python Kubernetes client (see in this file).

Kubernetes client libraries

There is a number of official client libraries in different programming languages for the Kubernetes REST API.

Go client

Client library in Go: https://github.com/kubernetes/client-go

Official tools like kubectl and kubelet use this library.

Other clients

All official Kubernetes client libraries in other languages are hosted in this GitHub organisation: https://github.com/kubernetes-client

2018-11-08

Finalize EKS cluster creation

As mentioned in the previous note, the steps for creating an EKS cluster are:

  • EKS service role
  • VPC
  • Security groups (control plane security group and worker nodes security group)
  • Control plane
  • Worker nodes

All this can be done with CloudFormation. It can actually be done with a single CloudFormation template.

After that, there is however one more step necessary to finalise the cluster. This step consists in applying a ConfigMap Kubernetes resource to the cluster, and this ConfigMap must contain the ARN of the IAM role that has been assigned to the worker nodes in the CloudFormation template. This last step will enable the worker nodes to actually join the cluster.

This step is executed with kubectl. In addition, kubectl depends on a tool called AWS IAM Authenticator. So we have to do two things first, install AWS IAM Authenticator and configure kubectl.

Install AWS IAM Authenticator

This has to be done only once.

The AWS IAM Authenticator is used by kubectl to handle authentication with the Kubernetes cluster.

Install the aws-iam-authenticator binary as follows:

go get -u -v github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator

And make sure that the aws-iam-authenticator binary is in the PATH.

The documentation for this step is here.

Configure kubectl

As with other managed Kubernetes service providers, once the cluster has been created, we need to "point" kubectl to the new cluster. Concretely, this consists in creating or updating the ~/.kube/config file with all the required information for our new cluster.

The AWS CLI provides a command to do this automatically:

aws eks update-kubeconfig --name <ClusterName>

After that, kubectl should be all set up to be used with our new cluster. You can test it, for example, with kubectl cluster-info.

The documentation for this step is here.

Enable worker nodes to join the cluster

Now we can do the final step, that is, creating the ConfigMap Kubernetes resource that enables the worker nodes to join the cluster (at this point, the worker nodes have not yet joined the cluster, which you can verify with kubectl get nodes).

To do this, create the following aws-auth-cm.yaml file, or download it from here:

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: <Role ARN>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

Replace <Role ARN> with the ARN of the role that has been assigned to the worker nodes in the CloudFormation template.

Then, apply the ConfigMap to the Kubernetes cluster as follows:

kubectl apply -f aws-auth-cm.yaml

The worker nodes will now join the cluster. You can watch this process with kubectl get nodes --watch. When all the nodes have a status of Ready, the cluster is complete.

The documenatation for this step is here under To enable worker nodes to join your cluster.

After that, the cluster is complete and ready, and you can deploy your Kubernetes application with kubectl.

2018-11-06

AWS Concepts

VPC

  • A VPC is a range of IP addresses
  • You can have multiple VPCs with the same range of IP addresses (VPCs are isolated from each other)
  • It is recommended to use subsets of RFC-1918 IP address ranges:
    • 10.0.0.0/8
    • 172.16.0.0/12
    • 192.168.0.0/16
  • The IP address CIDR block for a VPC must be between x.x.x.x/16 and x.x.x.x/28
  • A VPC is tied to a region, but spans all availability zones of this region

Subnet

  • A subnet is a subset of the VPC address range
  • A subnet is tied to an availability zone (a subnet cannot span multiple availability zones)
  • You can have one or more subnets within a VPC
  • You can have multiple subnets in the same availability zone
  • The IP address CIDR block for a subnet must be between x.x.x.x/16 and x.x.x.x/28 (and a subset of the VPC CIDR block)
  • The IP address ranges of different subnets in the same VPC must not overlap
  • The first four and the last IP address in a subnet IP address range are reserved for AWS

Elastic Container Service for Kubernetes (EKS)

  • EKS runs a Kubernetes control plane (master nodes) for you
  • The master nodes are fully managed by AWS
  • The Kubernetes control plane can be created in a specific region (currently only us-east-1, us-west-2, eu-west-1)
  • The master nodes do not run in your account
    • They run in an account managed by AWS (in your selected region)
    • There is no way to see the master node instances in your account
  • EKS creates three master nodes in three availability zones (the number of three is not officially specified, but reasonable)
    • You cannot find out or define in which concrete availability zones the master nodes are
    • You cannot change the number of master nodes that EKS creates
  • It is recommended to use a separate VPC for each EKS cluster

Steps to create an EKS cluster

Each step can be implemented as a separate CloudFormation template, or multiple steps can be combined in a template. Actually, all the steps can be combined in a single template file.

0. Create EKS service role

  • This is the IAM role that EKS assumes when creating new resources in your account (e.g. load balancer)
  • You need to do this only once and then the same role can be reused for multiple EKS clusters

1. Create VPC

  • Create a VPC with the following constraints:
    • At least two subnets in two availability zones (see here)
    • It is recommended that at least one subnet is public (used for Internet-facing Kubernetes resources) and at least one subnet is private (for worker nodes), see here
      • EKS examines the route table for your subnets to identify whether they are public or private. Public subnets have a route directly to the internet using an internet gateway, but private subnets do not. (source)
    • DNS support and DNS hostnames enabled (see here)
  • The VPC must be in a region that supports EKS (us-east-1, us-west-2, eu-west-1)

2. Create EKS control plane

  • Create a Cluster resource, which needs the following parameters:
    • EKS service role ARN: the ARN of the EKS service role from step 0
    • A list of subnet IDs: the IDs of all the subnets created for the VPC in step 1
      • There must be subnets from at least two availability zones
    • Control plane security group ID: the ID of a security group for the control plane.
      • This security group defines what traffic can be sent from the control plane to the worker nodes, and vice versa (see here).
      • The rules for this security group will be defined later when creating the worker nodes
  • Creating a Cluster resource behind the scenes creates a Kubernetes control plane consisting of multiple (three) master nodes across mutiple (three) availability zone in your region
    • These master nodes run in an account managed by AWS and not in your account (see here)
  • This operation also creates an elastic network interface in each provided subnet
    • These network interfaces are used for the communication between worker nodes and the master nodes outside of your account
    • The network interfaces are associated with the control plane security group
  • This operation also creates an HTTPS Kubernetes API server endpoint that the worker nodes in your cluster use to communicate with the control plane, and that you also use from your local machine with kubectl
  • More details about the EKS control plane here

3. Create worker nodes

  • EKS worker nodes are normal EC2 instances that run in your account
  • The instances must run in the subnets that have been indicated to Cluster resource in step 2 (these subnets have been configured with an elastic load balancer by the Cluster resource, which is necessary for the communication between worker nodes and control plane)
  • If these instances connect to the control plane, they become part of the EKS cluster
  • The Amazon EKS-optimized AMI is an AMI that si pre-configured with Docker, kubelet, AWS IAM Authenticator, and a bootstrap script (/etc/eks/bootstrap.sh) that allows the worker nodes to connect to the control plane automatically
    • The AMI has different IDs depending on the region wher it is used (see here):
      • us-east-1: ami-0440e4f6b9713faf6
      • us-west-2: ami-0a54c984b9f908c81
      • eu-west-1: ami-0c7a4976cb6fafd3a
  • Most of the time, the instances that make up the EKS worker nodes are created as an AutoScalingGroup
  • The LaunchConfiguration of the AutoScalingGroup contains a special UserData script that is executed on the instances on launch and that connects the instance to the EKS control plane:
    #!/bin/bash
    set -o xtrace
    /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArguments}
    /opt/aws/bin/cfn-signal --exit-code $? --stack  ${AWS::StackName} --resource NodeGroup  --region ${AWS::Region}
    
    • The script executes the bootstrap.sh script which detects and connects to the control plane
    • After that, the script uses cfn-signal to signalise to CloudFormation that the instance is now ready
    • Note: set -o xtrace is the same as set -x and simply prints out each command before executing it (see here)

Security groups

There are two security groups required:

  • Control plane security group
  • Worker node security group

The rules that these security groups must have are defined here.

2018-11-03

Create AWS EKS Cluster with CloudFormation

Tutorial

Steps

  1. Create VPC
  2. Create cluster (control plane)
  3. Create worker nodes

These three steps are executed as separate CloudFormation files.

Create VPC

Use this template: https://amazon-eks.s3-us-west-2.amazonaws.com/cloudformation/2018-08-30/amazon-eks-vpc-sample.yaml

Instructions here: https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html#vpc-create

Another example template is in the AWS VPC Quick Start: https://fwd.aws/px53q

Create cluster

Use something like this template: https://github.com/thestacks-io/eks-cluster/blob/master/cluster/cluster.stack.yml

Create worker nodes

Use one of the following templates:

The second template has been modified in August 2018, which is described here.

2018-10-24

AWS SAM lambda function permissions

When not defining a policy to give the lambda function permission to publish to a specific SNS topic like this: If you don't do this, then the application still works locally with sam local start-api. However, when the application is deployed to AWS, triggering the function produces the following error:

If your lambda function code contains the publishing of a message to SNS via the AWS SDK and you don't set any role or policies for your lambda function, then you get the following error when your lambda function is triggered on AWS:

{
  message: "User: arn:aws:sts::202449302273:assumed-role/sam-hello-world-3-HelloWorldFunctionRole-1AHR3DKFEBKPW/sam-hello-world-3-HelloWorldFunction-4WID1DG93XFW is not authorized to perform: SNS:Publish on resource: arn:aws:sns:us-east-1:202449302273:Daniel",
  code: "AuthorizationError",
  time: "2018-10-24T13:58:53.606Z",
  requestId: "27be2bb5-3811-53b3-aee7-7fff44cc6a0a",
  statusCode: 403,
  retryable: false,
  retryDelay: 25.668875729293816
}

This is because by default the lambda function gets a role that has no permissions except for creating logs in CloudWatch. If you need more permissions, you can append these permissions as a set of policies to the default role.

The following adds permission to publish messages to the SNS topic MyTopic to the lambda function:

Properties:
  Policies:
    - SNSPublishMessagePolicy:
        TopicName: MyTopic

The above policy SNSPublishMessagePolicy is a predefined SAM policy template. The full list of available SAM policy templates and their definitions can be found here.

If you don't find a SAM policy template that suits your needs, you can also define a policy from scratch as a IAM policy object like in CloudFormation.

2018-10-23

AWS Serverless Application Model (SAM)

SAM is an extension of AWS CloudFormation and it allows to define the resources needed for an AWS Lambda application.

An AWS Lambda application is an application that includes at least one AWS Lambda function (and most of the time other AWS services that serve as triggers and downstream services of a Lamda function).

Using SAM

To create a Lambda application with SAM, you write a SAM template in SAM syntax (which is an extension of CloudFormation syntax). This is typically a YAML file called template.yml.

In this template, you define the resources for your Lambda application (Lambda functions, DynamoDB tables, S3 buckets, API Gateways, etc.).

You also have to write the code for your Lambda functions, and you reference this code from the Lambda function definitions in the SAM template.

Once this is done, you package and deploy the application. This can be done with the AWS CLI or the AWS SAM CLI. It is identical to packaging and deploying a CloudFormation application as a CloudFormation stack.

SAM syntax

The SAM syntax specification is here.

In particular, SAM builds on CloudFormation and introduces several new objects. The most important one is the AWS::Serverless::Function resource type.

This resource type defines an AWS Lambda function, a corresponding IAM execution role, and the event source mappings, that is the events that trigger this lambda function to be executed.

The AWS::Serverless::Function resource has a series of properties of which Handler and Runtime are required. Handler defines the file and function name of the code to be executed when the function is triggered. Runtime defines one of the supported runtime environments on which the lambda function code runs.

Another important property of AWS::Serverless::Function is Events. This property defines the event source objects that trigger the function. The supported event source object types are also defined by the SAM specification here.

In particular, the supported event source types are S3, SNS, Kinesis, DynamoDB, SQS, Api, Schedule, CloudWatchEvent, CloudWatchLogs, IoTRule, AlexaSkill.

Minimal SAM example

A Lambda appliation consisting of a single Lambda function, no event source and no downstream actions on other AWS services (except the default CloudWatch logs). The Lambda function simply returns a string value.

Define

Defining the Lambda application

template.yml:

AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs8.10

Note that in index.handler, index indicates the filename that contains the handler function, and handleris the name of the handler function. You can use any names you want, but the names provided here and the JavaScript filename and handler function name must match.

Defining the Lambda function

index.js:

exports.handler = async function(event, context) {
    return 'Hello World!';
};

Note that for the nodejs6.10 runtime, that does not have async/await, it would be the following:

exports.handler = function(event, context, callback) {
    callback(null, 'Hello World!');
};

Deploy

  1. Create an S3 bucket (you can also just use an existing S3 bucket in step 2)

    aws s3 mb s3://my-bucket
  2. Package (upload artifact to the S3 bucket from step 1)

    sam package \
        --template-file template.yml \
        --s3-bucket my-bucket \
        --output-template-file package.yml

    Note that you can use aws cloudformation package instead of sam package. These are aliases.

  3. Deploy application

    sam deploy \
        --template-file package.yml \
        --stack-name sam-hello-world \
        --capabilities CAPABILITY_IAM

    Note that you can use aws cloudformation deploy instead of sam package. These are aliases.

After the deployment, your Lambda application is listed on https://console.aws.amazon.com/lambda and the corresponding CloudFormation stack is listed on https://console.aws.amazon.com/cloudformation.

If you make edits to either template.yml or index.js, just repeat steps 2 and 3.

Delete

To delete the application from AWS, you have to use the AWS CLI:

aws cloudformation delete-stack --stack-name sam-hello-world

SAM CLI

AWS SAM provides a CLI that makes certain tasks easier:

  • sam init
    • Generate a starter SAM application
  • sam validate
    • Validate a SAM template file
  • sam package
    • Package a SAM application and upload to S3 (ready for deployment)
    • Alias for aws cloudformation package (see usage, type aws cloudformation package help)
  • sam deploy: alias for aws cloudformation deploy
    • Deploy a packaged SAM application from S3 to AWS
    • Alias for aws cloudformation deploy (see usage, type aws cloudformation deploy help)
  • sam logs
    • Fetch the logs of a deployed Lambda function on AWS
  • sam local
    • Provide some local development tools (see below)

The most useful of these commands is clearly sam local. All the other commands are not really necessary and you could do without.

SAM CLI local execution

The SAM CLI allows to execute a Lambda function locally, that is, without deploying them to AWS. This is done with the sam local command.

The sam local command has the following sub-commands:

  • invoke
  • generate-event
  • start-lambda
  • start-api

invoke

Directly invoke a Lambda function.

Example:

echo '{"data": "foo"}' | sam local invoke HelloWorldFunction

This invokes the HelloWorldFunction Lambda function (as defined in template.yml) passing it the specified JSON data as the event data (that is, the data the handler function receives as the event argument).

If you don't care about the event data, you can use the --no-event option:

sam local invoke --no-event HelloWorldFunction

generate-event

This sub-command only generates output and doesn't invoke anything. The generated output is event data as produced by the events of different AWS services that can trigger a Lambda function. You can then pipe this output into the invoke sub-command to simulate that your Lambda function being triggered by a specific event.

The available event types are: alexa-skills-kit, alexa-smart-home, apigateway, batch, cloudformation, cloudfront, cloudwatch, codecommit, codepipeline, cognito, config, dynamodb, kinesis, lex, rekognition, s3, ses, sns, sqs, stepfunctions.

Example:

sam local generate-event s3 put | sam local invoke HelloWorldFunction

start-lambda

This sub-command allows to simulate a local AWS Lambda service. It doesn't invoke the Lambda function, but it allows to invoke the Lambda function as you would invoke any of your Lambda functions that are already deployed on AWS.

sam local start-lambda

The above starts a local Lambda service with endpoint http://127.0.0.1:3001. You can now invoke your local Lambda function with the same tools that you use to invoke Lambda functions on AWS, that is, with the AWS CLI or AWS SDK.

Invoking the local Lambda function with the AWS CLI:

aws lambda invoke --function-name HelloWorldFunction --endpoint-url http://127.0.0.1:3001 --no-verify-ssl out.txt

start-api

This can only be used if your Lambda application uses API Gateway as an event source.

In this case, it starts a local API Gateway service which triggers your Lambda function as defined in template.yml.

sam local start-api

The above starts a local API Gateway with endpoint http://127.0.0.1:3000.

You can then make requests to this API, for example, with curl:

curl http://127.0.0.1:3000

Alternatives to AWS SAM

  • Serverless Framework
    • Can do the same as AWS SAM but for various cloud providers
    • A good comparison between AWS SAM and Serverless Framwork is here
  • Terraform
    • Terraform can also be used to deploy Lambda applications to AWS
    • Terraform is an alternative to AWS CloudFormation (comparison here) and AWS SAM is an extension of CloudFormation

2018-10-21

Mailgun delivery failures

Microsoft email server rejecting messages from Mailgun

Emails sent via Mailgun to a @hotmail.com (Hotmail = Outlook = Microsoft) email address were not delivered. Mailgun reported a failed event in the logs (with severity=permanent, that is a "permanent failure").

In other words, this means, that Mailgun's attempt to send this message to the Hotmail email server was rejected by the Hotmail email server. The error message provided by the Hotmail email server was as follows:

5.7.1 Unfortunately, messages from [184.173.153.200] weren't sent. Please contact your Internet service provider since part of their network is on our block list (S3140). You can also refer your provider to http://mail.live.com/mail/troubleshooting.aspx#errors. [CO1NAM05FT019.eop-nam05.prod.protection.outlook.com]

This means that the IP address where the message was sent from (i.e. a Mailgun server) is on a blacklist of Microsoft, and so the Hotmail email server did not accept this message.

The bad thing was that I discovered this failed deliveries only many days after they happened. So, now I want to set up an alert that notifies me about failed deliveries immediately.

Mailgun events

Mailgun tracks events for everything that happens with outgoing or incoming emails. The list of tracked events is here int the quickstart and here in the user manual). The most important events are:

  • accepted: Mailgun accepted the message (either an outgoing email from the user or an incoming email from the sender's email server)
  • rejected: Mailgun rejected the message (" ")
  • delivered: Mailgun could successfully deliver the message to the target email server (either an outgoing email to the recipient's email server, or an incoming email that is being forwarded to the user's email server)
  • failed: the target email server rejected the message from Mailgun

The typical sequence of events for sending and receiving emails is as follows:

  • Sending: accepteddelivered
  • Receiving: acceptedaccepted (routed)delivered

If something goes wrong, then delivered can be replaced by failed, and this is what I want to be notified of.

How to get Mailgun events

There three ways to access Mailgun events (see here (Events)):

  • Mailgun GUI Logs tab
  • Mailgun API
  • Mailgun webhooks

GUI: in fact, the Logs tab in the Mailgun GUI is nothing else than a list of all the events that occurred.

API: the Mailgun Events API ( GET https://api.mailgun.net/v3/<domain>/events) allows to poll events with HTTP requests.

Webhooks: this allows to provide a URL to which Mailgun makes a POST request whenever an event occurs. This is what I want to do.

Webhooks

Mailgun webhooks are described here (Webhooks) in the user manual. Webhook URLs can be set in the GUI under the Webhooks tab.

In fact, it's pretty easy. You provide a URL for a specific event, and then whenever this event occurs, Mailgun makes a POST request to this URL. The body of the POST request is a JSON object that contains all the information about the event.

The JSON object has the following structure:

{  
   "signature": { ... },
   "event-data": { ... }
}

The signature object contains timestamp, token, and signature fields, and serves to verify the authenticity of the webhook POST request. How to do this is described here (Webhooks) under "Securing Webhooks".

The event-data object contains the actual data about the event and its structure is described here (Event Structure).

The webhooks data format and API has changed not too long ago, and the changes are described in this Mailgun blog post.

Set up webhooks for delivery failures

You can specify separate webhooks for "permanent failures" and "temporary failure". The first one correspond to a failed event with severity=permanent, and the second one to a failed event with severity=temporary.

The differences between permanent failures and temporary failures are described here (Tracking Failures).

In general, each of these cases means that the target email server rejected a message from Mailgun.

A permanent failure means that Mailgun will not attempt to deliver this message to the target email server again. The failure of the delivery is definitive (which means that the recipient will never receive this message).

A temporary failure means that Mailgun may try to deliver the message to the target email server again after a backoff time.

So, the really important thing to track are permanent failures. Temporary failures result in further delivery attempts which may then result either in a succesful delivery (delivered event) or in a permanent failure.

2018-10-19

Creating a Kubernetes deployment object from code

I created a Node.js program using the JavaScript Kubernetes client that creates a Kubernetes deployment of a single pod with the TG-Search core container. It also sets the name of this deployment to a unique identifier and sets the AMQP URI (that is passed as an environment variable to the container) according to the RabbitMQ user and vhost that would be created earlier (that's what I will do next). Creating a Kubernetes deployment works very well now.

RabbitMQ Management REST API

The RabbitMQ Management REST API allows to do the same as the Management Web UI and the rabbitmqctl. However, the latter two are intended to be used by humans, whereas the REST API is intended to be used programmatically.

The REST API is included in the management plugin (together with the Management Web UI). There is an entire chapter about the REST API in the book RabbitMQ in Action (Chapter 9).

Documentation

The documentation of the API is provided as an HTML page by the API itself under GET /api. For example, if RabbitMQ is running locally, just navigate to the following URL in the browser:

http://localhost:15672/api

Making requests

Requests to the API can be made with curl. For example, to list all vhosts (that the user guest has access to):

curl -i -u guest:guest http://localhost:15672/api/vhosts

Authentication

Note that all requests, except the one to /api for the documentation, require HTTP authentication. This can be provided with curl with the -u option.

The provided user must be a user of the target RabbitMQ server, and this user must have one of the tags administrator, management , policymaker, or monitoring (see here).

Note that, for example, for listing vhosts, a user will get only those vhosts back for which this user has access permissions.

Data formats

The API returns all data as JSON. Responses to PUT and DELETE requests never contain any data and have status 204 No Content.

The bodies of PUT and POST requests must also be in JSON and you have to set the Content-Type: application/json header. With curl you can set the body of the request with the -d option.

For example, for creating a queue:

curl -i -u guest:guest -X PUT \
  -H "Content-Type: application/json" \
  -d '{"auto_delete": true, "durable": false}' \
  http://localhost:15672/api/queues/my-vhost/my-queue

This creates the queue my-queue on the vhost my-vhost with the flags auto-delete: true and durable: false.

2018-10-18

Programmatically controlling Kubernetes

Kubernetes has a simple (but large) REST API. The kubectl command uses this REST API. There are client libraries in different languages that also use this REST API and allow to do everything that kubectl can do.

Kubernetes clients

The officially supported Kubernetes client libraries are here: https://github.com/kubernetes-client (plus the Go client which is here: https://github.com/kubernetes/client-go).

I looked a bit at the Java and JavaScript (Node.js) clients. First of all, the structure of these clients is very similar and quite simple:

  • There is a function for each API calll of the Kubernetes REST API
  • There is a class for each Kubernetes API object, which are all specified in the Kubernetes API reference

Swagger and OpenAPI: generating REST API code

The code for all these API functions and classes is auto-generated. In the case of the JavaScript client, this is done with Swagger Codegen. Codegen takes as input an OpenAPI specification of the API. For the JavaScript client, this file is swagger.json. The auto-generated file that Codegen outputs is api.ts.

I think that the OpenAPI specification of the Kubernetes API is provided officially by Kubernetes and updated with each new version of Kubernetes. I think so because the Java and JavaScript client use the same descriptions (Javadoc/TypeDoc) of the API function parameters, and the same names for these functions. → Yes, it is, see below.

So, Swagger and OpenAPI seems to be quite useful when working with REST APIs, because it frees you from writing very repetitive code (a function for each REST API call).

How to use the Kubernetes clients

The typical usage of these Kubernetes clients is like this:

  • Read a Kubernetes configuration
    • This is the $KUBECONFIG or ~/.kube/config file that is also used by kubectl
  • Create an API client with this configuration
  • Make Kubernetes API requests by calling methods on this API client

Kubernetes OpenAPI specification

Indeed, the OpenAPI specification used by the JavaScript client (and probably all other clients) is provided by Kubernetes and it can be retrieved through the API itself. That is, you can get it directly from the API server of any cluster. This is described here.

To get the swagger.json file used by the JavaScript client, just make a GET request to the /swagger.json endpoint (actually new to /openapi/v2 with an Accept: application/json header).

To easily do this, since you have to authenticate to the API server, you can run kubectl as a proxy:

kubectl proxy --port=8080

And then just make a GET request with curl or in the browser to localhost:8080/swagger.json. This approach is described here.

By the way, like this you can also access the Kubernetes API from your browser (as you would with kubectl). For example:

http://localhost:8080/api/v1/namespaces/default/services

To list all services in the default namespace. This is equivalent ot kubectl get services.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment