Deployment
This guide explains how to deploy async-service applications using systemd and Kubernetes. We'll use a simple example service to demonstrate deployment configurations.
Example Service
Let's start with a simple HTTP service that we'll deploy:
#!/usr/bin/env async-service
# frozen_string_literal: true
require "async/http"
require "async/service/managed/service"
require "async/service/managed/environment"
class WebService < Async::Service::Managed::Service
def start
super
@endpoint = @evaluator.endpoint
@bound_endpoint = Sync{@endpoint.bound}
end
def stop
@endpoint = nil
@bound_endpoint&.close
super
end
def run(instance, evaluator)
Console.info(self){"Starting web server on #{@endpoint}"}
server = Async::HTTP::Server.for(@bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme) do |request|
Protocol::HTTP::Response[200, {}, ["Hello, World!"]]
end
instance.ready!
server.run
end
end
module WebEnvironment
include Async::Service::Managed::Environment
def endpoint
Async::HTTP::Endpoint.parse("http://0.0.0.0:3000")
end
end
service "web" do
service_class WebService
include WebEnvironment
end
Save this as web_service.rb and make it executable:
$ chmod +x web_service.rb
Systemd Deployment
Systemd can manage your async-service application as a system service, providing automatic startup, restart on failure, and integration with system logging.
Service File
Create a systemd service file at /etc/systemd/system/my-web-service.service:
[Unit]
Description=My Web Service
After=network.target
[Service]
Type=notify
ExecStart=/usr/local/bin/bundle exec /path/to/web_service.rb
WorkingDirectory=/path/to/application
User=www-data
Group=www-data
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Key Configuration Points
- Type=notify: This is essential for
async-serviceto notify systemd when the service is ready. The service uses thesd_notifyprotocol via the$NOTIFY_SOCKETenvironment variable. - ExecStart: Points to your service script. Use
bundle execif you're using Bundler. - WorkingDirectory: Set to your application root directory.
- User/Group: Run the service as a non-privileged user.
- Restart=always: Automatically restart the service if it fails.
Installing and Managing the Service
# Reload systemd to recognize the new service
$ sudo systemctl daemon-reload
# Enable the service to start on boot
$ sudo systemctl enable my-web-service
# Start the service
$ sudo systemctl start my-web-service
# Check service status
$ sudo systemctl status my-web-service
# View service logs
$ sudo journalctl -u my-web-service -f
# Stop the service
$ sudo systemctl stop my-web-service
Verifying Readiness
The service will notify systemd when it's ready. You can verify this by checking the service status:
$ sudo systemctl status my-web-service
The service should show as "active (running)" once it has notified systemd of its readiness.
Kubernetes Deployment
Kubernetes can manage your async-service application as a containerized workload, providing scaling, health checks, and rolling updates.
Dockerfile
First, create a Dockerfile for your application:
FROM ruby:3.2
WORKDIR /app
# Install dependencies
COPY Gemfile Gemfile.lock ./
RUN bundle install --deployment --without development test
# Copy application files
COPY . .
# Expose the service port
EXPOSE 3000
# Set the notification log path
ENV NOTIFY_LOG=/tmp/notify.log
# Run the service
CMD ["bundle", "exec", "./web_service.rb"]
Deployment Configuration
Create a Kubernetes deployment file web-service-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-service
spec:
replicas: 2
selector:
matchLabels:
app: web-service
template:
metadata:
labels:
app: web-service
spec:
containers:
- name: web-service
image: my-registry/web-service:latest
ports:
- containerPort: 3000
name: http
env:
- name: NOTIFY_LOG
value: "/tmp/notify.log"
readinessProbe:
exec:
command:
- bundle
- exec
- bake
- async:container:notify:log:ready?
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web-service
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
Key Configuration Points
- readinessProbe: Uses the
async:container:notify:log:ready?bake task to check if the service is ready. This reads from theNOTIFY_LOGfile. - livenessProbe: HTTP health check to ensure the service is responding to requests.
- NOTIFY_LOG: Environment variable pointing to the notification log file path.
- replicas: Number of pod instances to run.
Deploying to Kubernetes
# Build and push the Docker image
$ docker build -t my-registry/web-service:latest .
$ docker push my-registry/web-service:latest
# Apply the deployment
$ kubectl apply -f web-service-deployment.yaml
# Check deployment status
$ kubectl get deployments
$ kubectl get pods
# View pod logs
$ kubectl logs -f deployment/web-service
# Check service endpoints
$ kubectl get svc web-service
# Scale the deployment
$ kubectl scale deployment web-service --replicas=3
# Update the deployment (rolling update)
$ kubectl set image deployment/web-service web-service=my-registry/web-service:v2
Verifying Readiness
Kubernetes will wait for the readiness probe to pass before routing traffic to the pod:
# Check pod readiness
$ kubectl get pods -l app=web-service
# Describe pod to see readiness probe status
$ kubectl describe pod <pod-name>
The pod will show as "Ready" once the readiness probe succeeds, indicating the service has notified that it's ready to accept traffic.
Notification Mechanism
Both systemd and Kubernetes deployments rely on the notification mechanism provided by async-container. The service uses instance.ready! to signal readiness:
- Systemd: Uses the
sd_notifyprotocol via the$NOTIFY_SOCKETenvironment variable (automatically handled byasync-container). - Kubernetes: Uses a log file (
NOTIFY_LOG) that the readiness probe checks using theasync:container:notify:log:ready?bake task.
This ensures that your service is only considered ready when it has actually started and is prepared to handle requests, preventing premature traffic routing and improving reliability.