Skip to content

Commit

Permalink
Adding ruby lambda layer (open-telemetry#918)
Browse files Browse the repository at this point in the history
* add ruby lambda layer draft

* update zip_ruby_layer script for passing name

* revision

* revision and add more info on readme

* add github action to build ruby lambda layer

* use https instead of http for rubygems.org

* add dependent bot

* Update .github/dependabot.yml

Co-authored-by: Tristan Sloughter <[email protected]>

* add sample handler as default runtime wrapper

* Update ruby/README.md

Co-authored-by: Tristan Sloughter <[email protected]>

* Update ruby/README.md

Co-authored-by: Tristan Sloughter <[email protected]>

* fix workflow

---------

Co-authored-by: Tristan Sloughter <[email protected]>
  • Loading branch information
xuan-cao-swi and tsloughter authored Jan 26, 2024
1 parent c146248 commit 4471945
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ updates:
opentelemetry-deps-python:
patterns:
- "opentelemetry-*"
- package-ecosystem: "bundler"
directory: "/ruby/src/layer"
schedule:
interval: "weekly"
groups:
opentelemetry-deps-ruby:
patterns:
- "opentelemetry-*"
other:
patterns:
- "*"
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
directory: 'nodejs'
- language: 'python'
directory: 'python'
- language: 'ruby'
directory: 'ruby'
- language: 'java'
directory: 'java'
- language: 'csharp'
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/release-layer-ruby.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: "Release Ruby Lambda Layer"

on:
# (Using tag push instead of release to allow filtering by tag prefix.)
push:
tags:
- layer-ruby/**

permissions:
id-token: write
contents: read

jobs:
build-layer:
runs-on: ubuntu-latest
outputs:
RUBY_SDK_VERSION: ${{ steps.save-ruby-sdk-version.outputs.RUBY_SDK_VERSION}}
steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2.0'

- name: Install SAM
run: |
apt-get update && apt-get install wget unzip make -y
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
./sam-installation/install
- name: Build
id: save-ruby-sdk-version
run: |
sam build -u -t template.yml
export GEM_PATH=$PWD/.aws-sam/build/OTelLayer/ruby/gems/3.2.0/
RUBY_SDK_VERSION=$(ruby -e 'require "opentelemetry-sdk"; puts OpenTelemetry::SDK::VERSION')
echo "RUBY_SDK_VERSION=$RUBY_SDK_VERSION" >> $GITHUB_OUTPUT
working-directory: ruby/src

- name: Zip the layer file
run: |
echo ${{ steps.save-ruby-sdk-version.outputs.RUBY_SDK_VERSION}}
./zip_ruby_layer.sh
working-directory: ruby/src
shell: bash

- uses: actions/upload-artifact@v3
name: Save assembled layer to build
with:
name: opentelemetry-ruby-layer.zip
path: ruby/src/opentelemetry-ruby-layer.zip

publish-layer:
uses: ./.github/workflows/layer-publish.yml
needs: build-layer
strategy:
matrix:
aws_region:
- ap-northeast-1
- ap-northeast-2
- ap-south-1
- ap-southeast-1
- ap-southeast-2
- ca-central-1
- eu-central-1
- eu-north-1
- eu-west-1
- eu-west-2
- eu-west-3
- sa-east-1
- us-east-1
- us-east-2
- us-west-1
- us-west-2
with:
artifact-name: opentelemetry-ruby-layer.zip
layer-name: opentelemetry-ruby
component-version: ${{needs.build-layer.outputs.RUBY_SDK_VERSION}}
runtimes: ruby3.2
release-group: prod
aws_region: ${{ matrix.aws_region }}
secrets: inherit
130 changes: 130 additions & 0 deletions ruby/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# OpenTelemetry Lambda Ruby

Scripts and files used to build AWS Lambda Layers for running OpenTelemetry on AWS Lambda for Ruby.

Requirement:
* [Ruby 3.2.0](https://www.ruby-lang.org/en/news/2022/12/25/ruby-3-2-0-released/)
* [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
* [Go](https://go.dev/doc/install)
* [Docker](https://docs.docker.com/get-docker)


## Building Lambda Ruby Layer With OpenTelemetry Ruby Dependencies

Build
```bash
sam build -u -t template.yml
```

Make sure the layer structure like below with your zip
```
ruby
└── gems
└── 3.2.0
├── build_info
├── doc
├── extensions
├── gems
├── plugins
└── specifications
```

Zip the file for uploading to ruby lambda layer

```bash
cd .aws-sam/build/OTelLayer/
zip -qr ../../../<your_layer_name>.zip ruby/
cd -

# or run following script
zip_ruby_layer.sh -n <your_layer_name>
```

### Why build layer this way

The default GEM_PATH for Lambda Ruby is $LAMBDA_TASK_ROOT/vendor/bundle/ruby/2.5.0:/opt/ruby/gems/2.5.0. Therefore, it's important to set the Ruby path in the format `/opt/ruby/gems/<ruby_version>`.

Reference for build ruby lambda layer
https://docs.aws.amazon.com/lambda/latest/dg/ruby-package.html


### Define the AWS_LAMBDA_EXEC_WRAPPER

There are two ways to define the AWS_LAMBDA_EXEC_WRAPPER that point to either binary executable or script (normally bash).

#### Method 1: define the AWS_LAMBDA_EXEC_WRAPPER in function from template.yml
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: OpenTelemetry Ruby Lambda layer for Ruby
Parameters:
LayerName:
...
Resources:
OTelLayer:
...
api:
...
function:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler # this is an example of the path

```

#### Method 2: directly update the environmental variable in lambda console: Configuration -> Environemntal variables

For more information about aws lambda wrapper and wrapper layer, check [aws lambda runtime-wrapper](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html#runtime-wrapper). We provide a sample wrapper file in `src/layer/otel-handler` as reference.

### Tracing and export trace to collector

To enable the default opentelemetry tracing, you can either initialize the opentelemetry ruby sdk in function code. To receive the trace, you can define the OTEL-related (e.g. OTEL_EXPORTER_OTLP_ENDPOINT) environmental variable and setup the opentelemetry collector accordingly.

For example:
```ruby
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.service_name = '<YOUR_SERVICE_NAME>'
c.use_all() # enables all instrumentation!
end

def lambda_handler(event:, context:)
# ... your code
end
```

Sample handler can be found here:
1. [splunk-otel-lambda](https://github.com/signalfx/splunk-otel-lambda/tree/main/ruby)

## Sample App

1. Make sure the requirements are met (e.g. sam, aws, docker, ruby version.)
2. Navigate to the path `cd ruby/sample-apps`
3. Build the layer and function based on template.yml. You will see .aws-sam folder after executed the command
```bash
sam build -u -t template.yml
# for different arch, define it in properties from template.yml
# Architectures:
# - arm64
```
4. Test with local simulation
```bash
sam local start-api --skip-pull-image

# curl the lambda function
curl http://127.0.0.1:3000
# Hello 1.3.0%
```

NOTE:
In `ruby/sample-apps/template.yml`, the OTelLayer -> Properties -> ContentUri is pointing to `ruby/src/layer/`. This is for local testing purpose. If you wish to deploy (e.g. `sam deploy`), please point it to correct location or zip file.

In this sample-apps, we use `src/layer/otel-handler` as default `AWS_LAMBDA_EXEC_WRAPPER`; to change it, please edit in `sample-apps/template.yml`


3 changes: 3 additions & 0 deletions ruby/sample-apps/function/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

# need this empty gemfile for success build
10 changes: 10 additions & 0 deletions ruby/sample-apps/function/lambda_function.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'json'
require 'opentelemetry-sdk'

def lambda_handler(event:, context:)
if defined?(::OpenTelemetry::SDK)
{ statusCode: 200, body: "Hello #{::OpenTelemetry::SDK::VERSION}" }
else
{ statusCode: 200, body: "Missing OpenTelemetry" }
end
end
45 changes: 45 additions & 0 deletions ruby/sample-apps/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: OpenTelemetry Ruby Lambda layer for Ruby
Parameters:
LayerName:
Type: String
Description: Lambda layer name to be published
Default: opentelemetry-ruby
Resources:
OTelLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: !Ref LayerName
Description: Opentelemetry Ruby layer
ContentUri: ./../src/layer
CompatibleRuntimes:
- ruby3.2
Metadata:
BuildMethod: makefile
api:
Type: AWS::Serverless::Api
Properties:
StageName: api
OpenApiVersion: 3.0.2
function:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./function
Handler: lambda_function.lambda_handler
Runtime: ruby3.2
Description: Build OTel Ruby Lambda layer and sample app from scratch
Policies:
- AmazonS3ReadOnlyAccess
Layers:
- !Ref OTelLayer
Events:
getEndpoint:
Type: Api
Properties:
RestApiId: !Ref api
Path: /
Method: GET
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler
5 changes: 5 additions & 0 deletions ruby/src/layer/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source 'https://rubygems.org'

gem 'opentelemetry-sdk'
gem 'opentelemetry-exporter-otlp'
gem 'opentelemetry-instrumentation-all'
8 changes: 8 additions & 0 deletions ruby/src/layer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

build-OTelLayer:
mkdir -p ruby
mkdir -p $(ARTIFACTS_DIR)/ruby/gems/3.2.0
bundler install --path ruby
cp -r ruby/ruby/3.2.0/* $(ARTIFACTS_DIR)/ruby/gems/3.2.0
cp otel-handler $(ARTIFACTS_DIR)/otel-handler
rm -rf $(ARTIFACTS_DIR)/ruby/gems/3.2.0/cache
14 changes: 14 additions & 0 deletions ruby/src/layer/otel-handler
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

if [ -z "${OTEL_SERVICE_NAME}" ]; then
export OTEL_SERVICE_NAME="$AWS_LAMBDA_FUNCTION_NAME";
fi

export LAMBDA_RESOURCE_ATTRIBUTES="cloud.region=$AWS_REGION,cloud.provider=aws,faas.name=$AWS_LAMBDA_FUNCTION_NAME,faas.version=$AWS_LAMBDA_FUNCTION_VERSION";
if [ -z "${OTEL_RESOURCE_ATTRIBUTES}" ]; then
export OTEL_RESOURCE_ATTRIBUTES="$LAMBDA_RESOURCE_ATTRIBUTES";
else
export OTEL_RESOURCE_ATTRIBUTES="$LAMBDA_RESOURCE_ATTRIBUTES,$OTEL_RESOURCE_ATTRIBUTES";
fi

exec "$@"
19 changes: 19 additions & 0 deletions ruby/src/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: OpenTelemetry Ruby Lambda layer for Ruby
Parameters:
LayerName:
Type: String
Description: Lambda layer name to be published
Default: opentelemetry-ruby
Resources:
OTelLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: !Ref LayerName
Description: Opentelemetry Ruby layer
ContentUri: ./layer
CompatibleRuntimes:
- ruby3.2
Metadata:
BuildMethod: makefile
40 changes: 40 additions & 0 deletions ruby/src/zip_ruby_layer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/sh

set -e
set -u

echo_usage() {
echo "usage: Build Lambda layer/application by SAM"
echo " -n <specify layer name>"
}


main() {
echo "running..."
layerName="opentelemetry-ruby-layer"

while getopts "n:h" opt; do
case "${opt}" in
h)
echo_usage
exit 0
;;
n)
layerName="${OPTARG}"
;;
\?)
exit 1
;;
:)
echo "Option -${OPTARG} requires an argument" >&2
exit 1
;;
esac
done

cd .aws-sam/build/OTelLayer/
zip -qr ../../../"$layerName".zip ruby/
cd -
}

main "$@"

0 comments on commit 4471945

Please sign in to comment.