10 tips to get the best out of AWS Greengrass
AWS Greengrass is a fantastic technology: it extends AWS cloud to the edge and let us build serverless solutions that span cloud, edge, and on-prem compute. Read more!
Jun 08, 2023 • 14 Minute Read
AWS IoT Greengrass is a fantastic technology: it extends AWS cloud to the edge and let us build serverless solutions that span cloud, edge, and on-prem compute.
But as any new tech — it’s still a bit rough around some edges and far from being easy to use. While our AWS friends are working hard to make it more civilized, here are 10 tips to help you get the best out of AWS Greengrass right now. Quick reference:
- Nail your development setup
- Greengrass in Docker: proceed with caution (with 5 steps to get it going)
- Basic troubleshooting hints — logs, deployments & lambda locations, etc
- Checking connection
- Troubleshooting Lambda functions
- Getting inside Lambda container
- Name your Lambda for happy troubleshooting
- Hack a quick code change in Lambda on local Greengrass
- Lambda on-demand? Lambda long-lived? Both!
- Use systemd to manage greengrass lifecycle
These are PRO tips, and assume a basic grasp on AWS Greengrass which includes some hands-on experience. This tips are best suited for those running a real IoT project with AWS. If you are new to this services, please bookmark this and come back after reviewing Greengrass tutorial and AWS Greengrass Troubleshooting.
Development setup for AWS IoT Greengrass
There are many ways to set up a dev environment for different tastes — but some approaches are more efficient than others. Greengrassing via AWS web console is fine for a first tutorial, but after you fat-finger your subscriptions a few times you’ll be looking for a better way.
I prefer old-fashion local development, and I’m fanatical about GitOps and infrastructure-as-code. This influenced my setup:
The editor of choice, git, AWS CLI with named profiles to jump between testing and production accounts — all run on my Mac. Greengrass core software is installed on a Vagrant VM; all the pre-requisites and installations are codified in a Vagrantfile.
I use greengo.io to operate Greengrass Group as code. Greengo lets me define the entire group (core, subscriptions, policies, resources, etc) as a simple YAML. It then creates it in AWS as defined, takes care of creating Lambda functions in AWS from the local code. It downloads the certificates and configuration file that I put straight to Greengrass VM thanks to helper scripts. As a bonus, greengo
also knows to cleans it all up!
With all that in place, I edit lambda functions and greengrass definitions alike with convenience of my favorite editor. I make changes, update, deploy, rinse, repeat. I jump onto Greengrasss VM with vagrant ssh
to check on greengrass well-being via logs, start/stop the daemon, explore it and pull various tricks described below.
All the work is captured as code, tracked by git, goes to GitHub right off my laptop, and can be easily reproduced. A complete code example — a codified AWS ML inference tutorial — is on GitHub for you to check out and reproduce: dzimine/aws-greengrass-ml-inference.
This setup serves me well, but this is of course not the only approach. You can take a three common ways to create your Lambda functions with AWS and creatively extend them to your Greengrass development. You might like Cloud9 + Greengrass on AmazonVM, as Jan Metzner demonstrated on IOT402 Workshop at re:Invent 2018.
If you’re a master of CloudFormation templates, check out the elegant GrassFormation and the development flow that comes with it. Whichever you choose, do yourself a favor — nail your development setup first.
Update March 18/2019: AWS Greengrass added support for CloudFormation templates. This is great, now you can use it raw, or with the tool of your choice like serverless framework or SAM CLI.
AWS Greengrass in Docker: proceed with caution
I managed Greengrass in Docker before the official support in v.1.7, using a couple of docker tricks: devicemapper
storage driver instead of default overlay2
, and --privileged
flag. It worked, and had no limitations on Greengrass functionality.
But with newer versions of Docker, Linux kernel, and Greengrass, it gradually stopped working. I abandoned the case, but you always try your luck at GitHub - dzimine/aws-greengrass-docker).
Now we can officially run AWS IoT Greengrass in a Docker Container. It is a great convenience, but a close look shows that it is more of a work-around. Making greengrass containers run inside docker container is admittedly difficult and unlikely ever suite for production. So AWS introduced a configuration option to run Lambdas outside of a container, as OS processes — on per-lambda or per-group basis.
Choosing this option brings limitations: Connectors, local resource access, and local machine learning models are not supported. Hey, not a big loss! Connectors are in their infancy — and for that matter I’d much rather want them for Lambdas and StepFunctions.
When lambdas run as OS processes they can access any local resources — so no need for configuring local resources. And a local ML stack can be easily custom-built into a Greengrass image to your liking.
Design considerations
A bigger concern with this approach, and overall with optional containerization for Lambdas, is that it smells hidden dependencies — making things fragile.The IoT workflow is about defining deployments in the cloud and pushing it to a fleet of devices. Some devices don’t support containerized Lambdas: be it greengrass running inside Docker or on a constrained OS with no containerization. From v1.7, Greengrass says “It’s OK, it will run as long as a group is opted out from containerization”. But devices don’t advertise their capabilities, nor do they have a guard to reject an unsupported group deployment. The knowledge of what group is safe to run on what devices must reside outside the system and inside designer’s head — admittedly not the most reliable store.
While enabling containerization for Lambda is a seemingly legit change in a group definition , it can break the deployment or cause function failures. Not to mention that running code with and without containerization may differ in many ways— I did hit weird code bugs that manifested only when containerized.
AWS Docs warn to use this option with caution, and prescribe use cases where running without containerization may worth the tradeoff. If you want to adopt Docker with Greengrass, I’d recommend goling all the way: drop containerization, run in Docker in dev and production, use the same containers.
AWS Greenhouse in Docker Tutorial
5 Steps for Greengrass in Docker
If the limitations outlined above don’t deter you from this approach, here’s how to run Greengrass in docker in 5 simple steps:
1. Get the zipped repo with Dockefile and other artefacts from the Cloud Front. Why not GitHub? May I clone it to my GitHub, or is it against the license? Ask AWS. For now, CloudFront:
curl https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.7.1/aws-greengrass-docker-1.7.1.tar.gz | tar -zx
2. Build the docker image with docker-compose
cd aws-greengrass-docker-1.7.1
PLATFORM=x86-64 GREENGRASS_VERSION=1.7.1 docker-compose build
3. Use greengo
to create a group: it will place the certificates and configuration for your into the /certs/
and /config
. Or die-hard with AWS console, download and sort out the certificates and config. Attention: The deployment must have all Lambdas opted out of containers: or pinned=True
in API, CLI, or greengo.yaml definition.
4. Run Docker with docker-compose:
PLATFORM=x86–64 GREENGRASS_VERSION=1.7.1 docker-compose up
5. Profit. For any complications, like a need for ARM image or a misfortune of running on Windows — open README.md and enjoy the detailed instructions.
You might ask “why not using Docker in a development setup instead of a heavy Vagrant VM”? You may: I do it myself sometimes, like a testbed for greengo
development.
But for a production-ready IoT deployment I prefer an environment to be the most representative of the target deployment — which is not always the case with GG Docker limitations.
AWS Greenhouse troubleshooting hints
When something goes wrong — which it likely will — here are the things to check:
- Make sure
greengrassd
is started.
Look for any errors in the output.
/greengrass/ggc/core/greengrassd start
If it doesn’t start, it most likely misses some pre-requisites, which you can quickly check by greengrass-dependency-checker
script. You can get the checker for the most recent version from aws-greengrass-samples repo.
There is also AWS Device Tester, a heavier option typically used by device manufactures to certify their device as Greengrass-capable.
2. Check system logs for any error.
Look under /greengrass/ggc/var/log/system/
, start with runtime.log
. There are few other logs with various aspects of greengrass functionality, described in AWS docs. I mostly look at runtime.log
:
tail /greengrass/ggc/var/log/system/runtime.log
It helps to set the logs to DEBUG
level (the default is INFO
). It is set up at Greengrass Group level via AWS console, CLI, API, or greengo
will do it for you. A group must be successfully deployed to apply the new log configuration.
But what if I am troubleshooting a failure in the initial deployment? Tough luck! There is a trick is to fake a deployment locally with just a logging config in it but it is quite cumbersome; it would be much better to set it up in config.json
file. Join my ask to AWS to do so:
To troubleshoot a failing AWS Greengrass group deployment, I need DEBUG log level. To set it to DEBUG, I need to set it in a group definition and deploy. Deployment is failing. To troubleshoot a failing deployment...
— Dmitri Zimine (@dzimine) March 2, 2019
CATCH 22!
May I please set log level in config? #awswishlist
3. Check the deployment
Once you trigger deployment and reaches “In Progress” state, a blast of messages pops up in runtime.log
as the greengrass handshakes, downloads the artefacts, and restarts the subsystems. The deployment artifacts are stored locally at /greengrass/ggc/deployment
:
group/group.json
- a deployment definition. Explore on your own:
cd /greengrass/ggc/deployment/group/
cat group.json | python -m json.tool
/greengrass/ggc/deployment/lambdas
- lambda code is deployed here. The content there is an extracted Lambda zip package. Just as you had it on your Dev machine before shipping up to AWS.
Key hint: these artifacts are what the local greengrass takes as an input. Toying with them is a great way to creatively mess with Greengrass.
I enthusiastically persuade you to write a profound logging in your Lambda code and set log levels to
DEBUG
for both system and user logging configuration.
Checking Greenhouse Deployment and Connection
Greengrass daemon starts, but nothing happens. Deployment is stuck forever with status “In progress’. What is wrong? Most likely, it is a connection problem.
Greengrass relies on MQTT (port 8883) for messaging and HTTPS (port 443) for downloading deployment artifacts. Make sure that your network connection allows the traffic.
Checking HTTPS is obvious, here is how to test your MQTT connection (source):
openssl s_client -connect
YOUR_ENDPOINT.iot.eu-west-1.amazonaws.com:8883
-CAfile root.ca.pem
-cert CERTIFICATE.pem
-key CERTIFICATE.key
Doesn’t this defeat the purpose? Greengrass needs HTTPS to get the deployment artifacts. If there is no way to switch it off 8443, how is it supposed to work? I didn’t experiment with this; if you suffer from over-secured networks please try and report your findings.
Update March 10/2019: AWS released version 1.8 which has a the discrepancy above: now you can configure HTTPS to use 443, too. And it works! I happen to have firewall issue this very day, applied the fix, and it did wonders. Hurray!
Troubleshooting Lambda Functions
When deployment is succeeded but nothing seems to be happening as expected, it’s time to look at Lambdas.
Check if the lambda functions are running:
ps aux | grep lambda_runtime
Look at the Lambda logs: dive under /greengrass/ggc/var/log/user/...
until you find them.
A function may crash before it creates a log file: for example, the core failed to even start the Lambda for reasons like missing dependencies or misconfigured runtime.
Try and run the lambda manually: now that you know where it is stored locally on the greengrass device — yes, /greengrass/ggc/deployment/lambdas/...
— go there and just run it in place, observe results.
Getting inside Lambda container
Greenness runs lambdas in containers (unless you explicitly opted out). As such, it sometimes happen that a function that runs fine when you launch it on device manually, still fails when running under greengrass.
Find out the PID of your lambda and get inside the container with nsenter:
sudo nsenter -t $PID -m /bin/bash
Now you are in the containerized environment of your Lambda function and can see and check mounted file systems, access to resources, dependencies, environment variables — everything.
This trick is easy for pinned (long-lived) functions, but common event-triggered lambdas don’t live long enough to jump in their container: you might need to temporarily pin them to be able to get in.
If modifying the lambda definitions in a Greengrass group, redeploying the group, and remembering to switch it back later feels like too much trouble, hack it on the device: set pinned=True
in the function section of group.json
under /greengrass/ggc/deployment/group
and restart the greengrassd
. The next deployment will reset your local hacks.
Name your Lambda for happy troubleshooting
What convention to use for naming Lambda functions and handlers? I used to call all my lambda entry points handler
and place it in function.py
files, like this:
# MyHelloWorldLambda/function.py
def handler():
return {'green': 'grass'}
But a check for running Lambda functions produced output like this:
ps aux | grep lambda_runtime
20170 ? Ss 0:01 python2.7 /runtime/python2.7/lambda_runtime.py --handler=function.handler
20371 ? Ss 0:01 python2.7 /runtime/python2.7/lambda_runtime.py --handler=function.handler
Not too informative, eh? Instead, consider naming either handler or function entry-point by the Lambda function name for more insightful output and easier troubleshooting. Then, finding Lambda process PID for the tricks in Tip 5 will be a breeze.
Hack a quick code change in Lambda on Greengrass
What is a typical development workflow for greengrass goes?
- Make a change to your Lambda function
- Test it locally
- Deploy the function to the AWS Lambda
- Update/increment the alias and make sure the greengrass lambda definition points to this very alias
- Deploy the group to your greengrass device
After these steps, the lambda is ready to be called. This is great when deploying final production code to a fleet of field devices.
But during developing, especially in debugging, we often need to try a quick code change, as tiny as printing out a value. The full workflow feels ways too long and cumbersome. While our AWS friends are hopefully working on integrating a local debugger (why not if Azure IoT edge does it?), here’s a quick hack I use:
- I
ssh
to my Vagrant VM with greengrass core - Go to the deployment folder
- Find the lambda in question and edit it in place
- Restart
greengrassd
, and test the change
If I don’t like the update, the next deploy will revert the change. If I do like the updated, I copy the code over the function code to my local development environment and deploy.
Here greengo.io comes handy again, taking care of cumbersome routines like repackaging & uploading lambda, incrementing versions, updating aliases, and repointing IoT lambda definitions to the right alias: all behind a simple command greengo update-lambda MY_FUNCTION
Lambda on-demand? Lambda long-lived? Both!
AWS Greengrass supports two lifecycle models for Lambdas: “on-demand” and “long-lived”.
Lambda On-Demand
The default behavior is “on-demand” which is the same as Lambda in the cloud — this is where the function is invoked by an event.
To trigger Greengrass “on-demand” lambda on MQTT event, you configure a subscription on a topic with the lambda function as a target. Greengrass will spawn the function execution on each topic message, pass a message as a parameter to a function handler, which then runs for the time not exceeding a configured timeout and shuts down. Variables and execution context is not retained between invocations.
Lambda Long-Lived
A “long-lived” function starts when Greengrass daemon starts which allows us to `pin` a function — make it long-running. This is a blessing for many use cases, like a video stream analysis, or protocol proxy where the function listens to the signals from BTLE or ZigBee devices and transfers them over MQTT.
The best kept secret? You can combine both. Yes, a long-lived function can be triggered on-demand. I can use this for good causes, like for keeping a non-critical state in memory between invocations.
For example: my function runs anomaly detection on the stream of device data with PEWMA algorithm. It must keep several previous data points to compute the averages. These data must persist between function executions triggered by data messages.
To achieve this, combine “long-lived” with “on-demand”:
- configure a subscription to fire the function on receiving the device data: greengrass dutifully calls the handler with the data payload as messages arrive
- make the function long-living: now I can just keep the state in memory, between handler invocations. If the Lambda restarts and looses the state, it’s OK: the algorithm will quickly recover so a true persistence doesn’t worth the trouble.
For a complete example, take a look at the AnomalyCatcher function code in my Greengrass IIoT prototype.
Remember that once the Lambda is configured “long-lived”, the functions won’t run in parallel: invocation are queued and handler is invoked one at a time. Check if it’s appropriate for your message volume, and mind your handler code from anything that might block a queue.
Use systemd to manage greengrass lifecycle
When your Greengrass device reboots, you would want greengrass to start automatically, wouldn’t you? Well, it won’t until you make it so. Use systemd to manage the life cycle of greengrass daemon process.
You’ll need to modify the useSystemd
flag to true in config.json,
and set up a systemd script, like the one in this simple example:
[Unit]
Description=Greengrass Daemon
[Service]
Type=forking
PIDFile=/var/run/greengrassd.pid
Restart=on-failure
ExecStart=/greengrass/ggc/core/greengrassd start
ExecReload=/greengrass/ggc/core/greengrassd restart
ExecStop=/greengrass/ggc/core/greengrassd stop
[Install]
WantedBy=multi-user.target
Here are detailed step-by-step instructions for Raspberry-PI. If your greengrass device supports another init system — like upstart, SystemV, or runit — check the manual and configure the greengrassd
as a daemon according to it.
I hope you enjoyed my 10 Greengrass tips and put them to a good use in your IoT projects. It took some pain to figure them out: your claps 👏 would be the best reward. Be sure to visit AWS Greengrass Troubleshooting page, and check AWS IoT Greengrass Forum for more.
Want more AWS goodness? Check these out:
Want to take your cloud skills and knowledge to the next level? Start your learning journey with A Cloud Guru's courses and learning paths!
Please add your own tips in the comments here for the rest of us. For more stories and discussions on or follow meon IoT, DevOps, and Serverless, follow me on Twitter @dzimine.