The world of DevOps and container orchestration is constantly changing, and picking the right tool can make a big difference in how efficiently microservices are deployed. One tool that stands out is HashiCorp Nomad. In this guide, we’ll dive into how you can deploy a simple microservice using Nomad on a DigitalOcean Droplet running Ubuntu server.
Nomad is a lightweight workload scheduler that manages application deployment and scaling across a cluster of machines. It supports multiple workload types, integrates with other HashiCorp tools, and is infrastructure agnostic. Nomad’s simplicity, versatility, scalability, resource efficiency, and integration make it an excellent choice for deploying and managing microservices.
Check out Nomad’s official documentation and its Comparison guide for more details and features.
Before we begin, make sure you have the following installed:
Or
First and foremost, you will need to get the SSH-key-id and Access Token, which will be used to run the doctl
command to create a Droplet.
Let’s set things up:
- doctl compute ssh-key list
Now, let’s create an Ubuntu Droplet:
- doctl compute droplet create my-droplet --size s-2vcpu-4gb --region nyc1 --image ubuntu-22-04-x64 --ssh-keys <YOUR_SSH_KEY_ID> --access-token '<ACCESS_TOKEN>' --output json"
The above command creates a new DigitalOcean Droplet named “my-droplet” with a specified size, region, image,SSH key ID and access token.
You can use the following command to get the supported sizes for Droplets and their hourly and monthly prices:
- doctl compute size list
You can refer to the doctl
commands reference to learn more and play around with doctl
.
Now, to SSH into your Ubuntu Droplet, we will first need the Public IP of the Droplet, which can be found using the following doctl
command:
- doctl compute droplet get my-droplet
Note the Public IPv4 address from the above command and then SSH to the Droplet from your local server.
Once you know the Public IPv4 address of your Ubuntu Droplet, you will need to add SSH keys to connect to your Droplet remotely from your local system via SSH.
Now that you have added your SSH keys, you can log in to the Ubuntu Droplet remotely via SSH:
- ssh root@<PUBLIC_IP>
You can refer to the following guides to install Nomad and Docker engine on an Ubuntu server:
Nomad installed on your DigitalOcean Ubuntu Droplet.
Docker Engine installed and running on your Ubuntu Droplet.
Once you have the Ubuntu Droplet deployed with Nomad and Docker installed, you can start Nomad in “dev mode” using the following command in the terminal:
- nomad agent -dev
You will observe an output like the one below:
Output
==> No configuration files loaded
==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Node Id: 51b0f49b-564b-5d1c-8e99-a17336c2a597
Region: Global (DC: dc1)
Server: true
Version: 1.6.2
==> Nomad agent started! Log data will stream in below:
2023-12-05T18:43:58.414+0530 [INFO] nomad.raft: initial configuration: index=1 servers="[{Suffrage:Voter ID:e707dbd2-be06-7cfc-dff0-a11714eec76f Address:127.0.0.1:4647}]"
2023-12-05T18:43:58.415+0530 [INFO] nomad.raft: entering follower state: follower="Node at 127.0.0.1:4647 [Follower]" leader-address= leader-id=
This will start the Nomad agent on your machine as the server and client components. In dev mode, Nomad runs on a single node and does not persist in any data, making it ideal for experimentation and development.
Once the Nomad agent starts running, you can access the Nomad web user interface by visiting http://localhost:4646 in your browser. In our case, we need some tunneling to access the Nomad UI from our Ubuntu Server. We can use Pinggy, a tool that provides Public URLs for Localhost. It generates a temporary URL through which one can access the Nomad UI.
- ssh -p 443 -R0:localhost:4646 -L4300:localhost:4300 qr@a.pinggy.io
Copy and run the above SSH command on your Ubuntu server to get Nomad UI URLs that can be accessed from your phone browser or any other device.
Copy the URLs displayed in the above screenshot.
Here is the Nomad UI. You can view the Job details, details of the deployment, etc, from this UI throughout the tutorial.
Nomad jobs are defined by the Nomad job specification, also known as “jobspec”. The job spec schema is written in HCL.
The job specification is divided into smaller sections, which you can find in the navigation menu. Nomad HCL is parsed in the command line and then sent to Nomad in JSON format via the HTTP API.
Let’s talk about each in a bit more detail:
task - Nomad uses tasks as the smallest unit of work. These tasks are executed by task drivers such as docker or exec, which allow Nomad to be flexible in the tasks it supports. Tasks specify their required task driver, configuration for the driver, constraints, and resources required.
group -A group refers to a set of tasks that are executed on a single Nomad client."
Job - A job is the core unit of control for Nomad and defines the application and its configurations. It can contain one or many tasks.
job specification - A job specification, also known as a jobspec defines the schema for Nomad jobs. This describes the type of job, the tasks and resources necessary for the job to run, job information like which clients it can run on, and more.
allocation - An allocation is a mapping between a task group in a job and a client node. When a job is run, Nomad will choose a client capable of running it and allocate resources on the machine for the task(s) in the task group defined in the job.
The above constructs make up a Job in Nomad.
Now that Nomad is operational, we can schedule our first job. Our initial task will involve running the http-echo
Docker container. This straightforward application generates an HTML page displaying the arguments passed to the http-echo process, such as “Hello World”. The process dynamically listens on a port specified by another argument.
Let’s create a job file that ends with the name “microservice.nomad”—all the job files in Nomad end with .nomad
suffix .
-
- job "http-microservice" {
-
- datacenters = ["dc1"]
-
- group "echo" {
-
- count = 1
-
- task "server" {
-
- driver = "docker"
-
- config {
-
- image = "hashicorp/http-echo:latest"
-
- args = [
-
- "-listen", ":${NOMAD_PORT_http}",
-
- "-text", "Hello and welcome to ${NOMAD_IP_http} running on port ${NOMAD_PORT_http}. This is my first microservice deployment using Nomad.",
-
- ]
-
- }
-
- resources {
-
- network {
-
- mbits = 10
-
- port "http" {}
-
- }
-
- }
-
- }
-
- }
-
- }
In this example, we defined a job called http-microservice
, set the driver to use docker
, and passed the necessary text and port arguments to the container. As we need network access to the container to display the resulting webpage, we define the resources section as requiring a network with a port that Nomad dynamically chooses from the host machine to the container during runtime.
Nomad supports using dynamic port assignment, i.e., you don’t need to specify a port.
We can submit/run
new or update existing jobs with the nomad job run
command, using job files that conform to the job specification format.
- nomad job run microservice.nomad
Output:
Output
==> 2023-12-06T13:58:25+05:30: Monitoring evaluation "bd6dd191"
2023-12-06T13:58:25+05:30: Evaluation triggered by job "http-microservice"
2023-12-06T13:58:25+05:30: Evaluation within deployment: "b8499a02"
2023-12-06T13:58:25+05:30: Evaluation status changed: "pending" -> "complete"
==> 2023-12-06T13:58:25+05:30: Evaluation "bd6dd191" finished with status "complete"
==> 2023-12-06T13:58:25+05:30: Monitoring deployment "b8499a02"
✓ Deployment "b8499a02" successful
2023-12-06T13:58:25+05:30
ID = b8499a02
Job ID = http-microservice
Job Version = 0
Status = successful
Description = Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress
Deadline
echo 1 1 1 0
2023-12-06T14:08:23+05:30
We can check the status of a job using the command:
- nomad job status http-microservice
OutputID = http-microservice
Name = http-microservice
Submit Date = 2023-12-13T13:44:55+05:30
Type = service
Priority = 50
Datacenters = dc1
Namespace = default
Node Pool = default
Status = running
Periodic = false
Parameterized = false
Summary
Task Group Queued Starting Running Failed Complete Lost Unknown
echo 0 0 1 0 0 0 0
Latest Deployment
ID = 9a72c817
Status = successful
Description = Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
echo 1 1 1 0 2023-12-13T14:00:33+05:30
Allocations
ID Node ID Task Group Version Desired Status Created Modified
84d70c8f aa3f6a1a echo 0 run running 5m19s ago 5m6s ago
Please make a note of the allocation ID in the above output:84d70c8f. Allocation ID is used to troubleshoot deployment issues in Nomad.
You can also access the Nomad UI using the below command to check the Jobs and other important details of your deployment on your local desktop:
- nomad ui <job_name>
In our case:
- nomad ui http-microservice
We can use the command nomad alloc-status <allocation_id>
to get the port on which the microservice is deployed. When deploying jobs with dynamic ports, finding the exact port on which a specific task has been deployed can be challenging because the port is dynamically allocated at runtime. This command is also great for troubleshooting any deployment issues in Nomad.
We can get the allocation_id using the command nomad job status http-microservice
specified in the above section.
- nomad alloc-status 84d70c8f
Output:
Output
ID = 84d70c8f-0687-e1c7-27fe-172ae274580b
Eval ID = 404f0d71
Name = http-microservice.echo[0]
Node ID = aa3f6a1a
Node Name = XXXXXXXX
Job ID = http-microservice
Job Version = 0
Client Status = running
Client Description = Tasks are running
Desired Status = run
Desired Description = <none>
Created = 11m3s ago
Modified = 10m50s ago
Deployment ID = 9a72c817
Deployment Health = healthy
Task "server" is "running"
Task Resources:
CPU Memory Disk Addresses
0/100 MHz 9.0 MiB/300 MiB 300 MiB http: 127.0.0.1:20004
Task Events:
Started At = 2023-12-13T08:20:23Z
Finished At = N/A
Total Restarts = 0
Last Restart = N/A
Recent Events:
Time Type Description
2023-12-13T13:50:23+05:30 Started Task started by client
2023-12-13T13:50:20+05:30 Driver Downloading image
2023-12-13T13:50:19+05:30 Task Setup Building Task Directory
2023-12-13T13:50:19+05:30 Received Task received by client
From the above, we can make a note of the address on which the microservice is running, which is 127.0.0.1:20004
, and send a curl request to get the status of the microservice.
- curl -i 127.0.0.1:20004
Outputcurl: (56) Recv failure: Connection reset by peer
anish@W2MK93VGYX nomad.d % curl -i http://127.0.0.1:20004
HTTP/1.1 200 OK
X-App-Name: http-echo
X-App-Version: 1.0.0
Date: Wed, 13 Dec 2023 08:24:59 GMT
Content-Length: 108
Content-Type: text/plain; charset=utf-8
Hello and welcome to 127.0.0.1 running on port 20004. This is my first microservice deployment using Nomad.
We can verify that the microservice has been successfully deployed from the above.
One of the key benefits of using Nomad is its ability to scale your microservices based on demand. To scale up your job, simply modify the job file to increase the count
parameter or use the nomad job scale <job> <count>
command.
Let’s scale our hello-world microservice:
- nomad job scale http-microservice 3
Output:
Output 2023-12-05T19:28:45+05:30: Evaluation status changed: "pending" -> "complete
==> 2023-12-05T19:28:45+05:30: Evaluation "84241aca" finished with status "complete"
==> 2023-12-05T19:28:45+05:30: Monitoring deployment "a26236c7"
✓ Deployment "a26236c7" successful
2023-12-05T19:28:58+05:30
ID = a26236c7
Job ID = http-microservice
Job Version = 1
Status = successful
Description = Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress
Deadline
echo 3 3 3 0
2023-12-05T19:38:57+05:30**
On the Nomad UI(http://localhost:4646/ui/jobs
), we can see the changes being reflected on the go:
Nomad offers a range of advanced features and concepts that can further enhance your deployment strategy:
1. Task Drivers:
Nomad supports multiple task drivers, allowing you to choose the best runtime environment for your applications. Explore different task drivers for various workload types.
2. Task Groups:
Organize your tasks into groups for logical separation and resource allocation. Task groups enable you to define specific constraints and settings for different sets of tasks.
3. Update Policies:
Nomad provides flexible update policies for controlling how your jobs are updated. Explore strategies like rolling updates to ensure zero downtime during deployments.
4. Service Discovery:
Utilize Nomad’s integration with Consul for seamless service discovery. This is crucial for dynamic microservices architectures where services need to locate and communicate with each other.
Congratulations! You’ve successfully deployed a simple microservice and explored some advanced features of Nomad. Nomad’s simplicity, versatility, and integration capabilities make it a compelling choice for DevOps engineers managing microservices at scale. Consider exploring more features and integrating Nomad into your broader infrastructure to maximize efficiency and scalability.
You can check out Nomad’s tutorial library and its comprehensive and detailed introduction guide to gain deeper insights into using Nomad.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!