Secure runtime variables in IronWorker with Manifold
IronWorker is a containerized background job manager that gives you incredibly fast, super-scalable architecture with almost no code. Because it handles tasks asynchronously, any web app can offload heavy lifting such as slow database operations or large data processing to IronWorker without slowing down your usersā experience.
But thereās one problemāif a background worker is talking to all of these services, where do the secrets go? Docker containers are fantastic packages for consistent code runtime, but itās no place for secrets to liveācached and available to everyone that can access that Docker image.
Manifold gives you a 2-minute setup to IronWorker (and many other cloud services) ā and it can also solve the problem of secrets for you, completely free. As developers ourselves, we built the tools weād like to exist. Weāve been using this internally for over a year, and think itās a pretty great solution.
For extra funāand to see how secrets work in practiceāweāll hook up IronWorker to another Manifold marketplace service, LogDNA, to enhance the experience with realtime logs. On IronWorker, you donāt get to view a single log until after a task is finished running (or erring š). So taking advantage of a cloud-based logging service to give you realtime updates is a huge win, especially for large tasks.
What can I use IronWorker for?
IronWorker is just one piece of the Iron.io devops suite of products. Using IronWorker, you can handle several incoming tasks at once from multiple sources, sort the queue by priority, and monitor the status of all of those concurrent tasks.
It takes care of big, slow, heavy stuff.
Iron.ioās post Top 10 Uses of a Worker System explains why workers are simply better for heavy tasks like image processing, transactional emails, and data sifting. Give it a read to, as the author put it: āChange the way you think and program.ā
Setup
Youāll need a few things to set up for this blog post:
- Sign up for an account on Manifold (itās secure, free, and you can cancel at any time).
- Install the Manifold CLI, and log in with manifold login
- Install the Iron.io CLI (Manifold will log in for us!)
- Install Docker, create a Docker ID if you havenāt already, and log in with docker login
1. Provisioning LogDNA and IronWorker
From your Manifold account, create a new project. Weāll call it iron-example in our post, but name it any kebab-case name youād like!
From there, provision **LogDNA **and IronWorker plans to the project.
Already have an existing IronWorker or LogDNA plan?
If possible, weād recommend signing up through our Dashboard to take advantage of single sign-on and aggregated billing, but you can still power your pre-Manifold account with our integrations.
To integrate your existing accounts, click the āBring your own serviceā button and entering your credentials: IRON_TOKEN and IRON_WORKER_PROJECT_ID for IronWorker; KEY for LogDNA.
Signing into LogDNA
Back in our project, we can now see the new LogDNA and IronWorker services present. Click Open LogDNA Dashboard.
Hovering over LogDNA in our project shows us a one-click Dashboard sign-in button
With Manifoldās single sign-on we get to skip signup and go straight to our projectās LogDNA dashboard. Click the Everything tab to see our appās logs.
Itās empty for now, but weāll come back to this screen in just a moment to see our logs.
2. Uniting the workers on Iron.io
Building a worker in Node.js
Now that we have LogDNA and IronWorker accounts managed in Manifold, letās get some worker code up.
Weāre starting with a bare-bones example Node.js app thatās already configured for Manifold + IronWorker. To view this code locally, run the following in a terminal window:
git clone [email protected]:manifoldco/iron-example-app.git
cd iron-example-app
npm i
This is our entire app, minus config:
So our appā¦
- ā¦authenticates with LogDNA using Manifold CLIās injected KEY
- ā¦doesnāt authenticate with IronWorker because thatās where our code will be running
- ā¦logs some simple text first,
- ā¦followed by our IronWorker payload with IronWorker.params() (weāll get back to that)
- ā¦and a final log showing us the jobās complete.
š³ Containerizing with Docker
IronWorker runs off Docker Hub, which is great news for the many dev teams already using it. And if youāre new to containerization, Manifold can help make adoption relatively painless.
Our app uses a custom Dockerfile (first letter uppercase, no extension) in the root folder of our application, configured for Manifold CLI within IronWorker.
Assuming Docker is running, and weāre logged in with docker login, weāll build a new Docker container and tag with -t. You can think of tags like versions of your code, so every time you make a change, you should give it a new tag. A common format is DOCKER_USERNAME/APP_NAME:VERSION (e.g.: manifold/iron-example:0.0.1). In the following examples, replace USERNAME with your Docker username. After itās built, weāll send it to Docker Hub.
docker build -t USERNAME/iron-example:0.0.1
docker push USERNAME/iron-example:0.0.1
š Tip: testing stuff, or pushing an unstable version? Append -rc.0, -rc.1, etc. to the end of your tag to keep your versions clean.
šļøā Pushing to Iron.io
Weāre letting Manifold handle our credentials, but IronWorker will need to authenticate into Manifold to pull secrets. Rather than give out our Manifold email & passwordāthatās not secure!āwe can create a special token just for this worker with read-credentials permissions just for this case:
manifold tokens create
Keep it secret, keep it safe! You wonāt be able to retrieve this later. But you can generate a new token, or deprecate an old token at any time with manifold tokens āhelp.
With that token, we can use Manifold to inject the IRON_TOKEN and IRON_PROJECT_ID secrets as runtime variables. To alert Iron.io to our image on Docker hub, run the following:
manifold run -p iron-example -- iron register -e "MANIFOLD_API_TOKEN=MY_MANIFOLD_TOKEN" USERNAME/iron-example:0.0.1
This is using manifold run to inject variables attached to our iron-example project (-p). Itās running the iron register command, injecting a single runtime variable (-e): our Manifold token from the previous step. Our final argument is the tag of the image we just pushed to Docker Hub.
Note: to improve security and performance, Manifold isnāt a proxy; it only injects the secrets. Your app connects directly to all these services with no middleman.
Checking our work
If we go back to our iron-project in the Manifold Dashboard, we can click the Open IronWorker Dashboard button to see our Dashboard:
Sign into IronWorker straight from Manifold
Once signed in, we should see our Docker image on the home screen:
Iron.io dashboard
š Woo! š Iron.io is now running our worker code, ready to be assigned tasks.
Recap for deployment
Say we make a change in our app, and need to update IronWorker to USERNAME/iron-example:0.0.2. Hereās a recap of what to do:
docker build -t USERNAME/iron-example:0.0.2 .
docker push USERNAME/iron-example:0.0.2
manifold run -- iron register -e "MANIFOLD_API_TOKEN=MY_MANIFOLD_TOKEN" USERNAME/iron-example:0.0.2
-
Re-build with docker build
-
Push to Docker Hub with docker push
-
Tell Iron.io about the new Docker Hub image with iron register (with manifold run logging in for us)
You can add a deploy NPM Script in package.json to automate this, and save a little typing:
TAG="USERNAME/iron-example:0.0.2" MANIFOLD_TOKEN="MY_TOKEN" npm run deploy
This is what the script looks like in our [package.json](http://(already present in our example, actually!):) (weāre also using a .manifold.yml file to set the project & team):
Note: this was the shortest amount of typing I could think of without committing a sensitive secret to version control. If you can think of an even shorter way to run this, leave a comment!
3. Queueing for everyone
Now comes the good part: actually seeing our app run. Sign in to your LogDNA dashboard again from your Manifold project, and navigate to Everything. Also sign into your IronWorker dashboard, and click on the name of your image.
Clicking on an image in IronWorker shows you all your tasks, along with the version that ran them, logs, and other pertinent data.
Youāll see a screen that shows you all your queued tasks, and which tasks ran successfully and which erred (green and red in the screenshot). This will be blank if you havenāt queued any. From that screen, click the Queue Task button to test our code.
IronWorkerās Queue Task screen
For now, all you need to fill out is Payload, in JSON format. For our example, weāll feed it the time-honored, classic dish:
{
"foo": "bar"
}
Click Queue Task and youāll see it run. Now, if everything worked as intended, pop over to LogDNA to see:
We see the thing we typed! Thatās a very good sign!
Our app now takes the payload received from IronWorker through IronWorker.params(), and logs it to LogDNA. This means that your code can be dynamic, accepting a wide range of inputs and tasks based on the data itās given. IronWorker.params() will be your entrypoint to any kind of data needed for your super special task.
Your worker is basically now an endpoint, ready for any task large or small. The secure connections are handled thanks to Manifold, and the parallelism is handled for you thanks to Iron. āØ
Next Steps
Running tasks manually gets old real quick, especially because weāre developers and will automate anything we have to do three times š. Now that we have a worker with running code and able to handle payloads, weāre getting into serious territory. Iāll end the post here, showing how to schedule tasks from the IronWorker dashboard, as well as programmatically from another application.
Scheduling Tasks from within IronWorker
IronWorker has a handy dashboard to create and manage scheduled tasks just one tab away! Click **scheduled tasks in **the navigation, followed by the Schedule Task in the top-right to be met with a familiar screen:
Here, we can also take a payload as with the manual task, but now we can schedule automatically recurring tasks, like a cron job on a server but with no server or cron jobs to manage.
If your app can run automatically, such as a newsletter or scraper, this is probably the best place to manage that schedule.
Queueing tasks from outside of IronWorker
Scheduled tasks are great, but most cases will usually involve some other application that needs to queue a job to our worker. Fortunately, the iron_worker NPM module we were using also comes with a .Client() built in:
Note: remember we didnāt have to authenticate for our worker running on IronWorker, but we will have to for outside apps.
So for any large, arduous, async job, we can simply kick it to IronWorker from any app, and go about our business. In case your stack isnāt in Node.js, fret not: IronWorker client has first-class support for Golang, Ruby, Java, PHP, Python, and .NET too!
We canāt schedule from outside our app, because ideally scheduling should be self-managing! To recap: you can schedule from the IronWorker CLI or the Dashboard, or you can queue from anywhere using the IronWorker client. Consider a different approach if youāre trying to schedule a repeat event programatically.
To learn more about queueing, see the docs on IronWorker client.
From here, the skyās the limit!
Just keep on pressinā on
Further Reading
- Building a Scheduled Newsletter in Ruby with IronWorker and Mailgun by Robin Percy
- Top 10 uses of IronWorker by Dylan Starnat
- IronWorker webhooks on the Iron.io docs
- Arguments and variables in Docker by Tim Speed