Semaphore UI: A Web Control Plane for Ansible - 夜莺博客

Semaphore UI: A Web Control Plane for Ansible

Ansible playbooks that live on one engineer's laptop are a bus factor waiting to happen. Semaphore is a lighter answer than AWX: a single Go binary with a web UI and API that runs Ansible, Terraform, OpenTofu, Bash, PowerShell and Python tasks, keeps the output of every run, and stores credentials encrypted rather than in a shell history. It is small enough to run on a management VM and structured enough to be the auditable entry point for changes. This guide covers a working install, the resource model, and the configuration choices that matter in production.

Install and first run

# Debian/Ubuntu package
wget https://github.com/semaphoreui/semaphore/releases/download/v2.19.12/semaphore_2.19.12_linux_amd64.deb
sudo apt install ./semaphore_2.19.12_linux_amd64.deb

# guided setup: database, admin user, config file
semaphore setup --config ./config.json

# or Docker, with SQLite for a lab
docker run --name semaphore -p 3000:3000 \
  -e SEMAPHORE_DB_DIALECT=sqlite \
  -e SEMAPHORE_ADMIN=admin \
  -e SEMAPHORE_ADMIN_PASSWORD=changeme \
  -e SEMAPHORE_ADMIN_NAME="Admin" \
  -e SEMAPHORE_ADMIN_EMAIL=admin@example.com \
  -d semaphoreui/semaphore:v2.19.12

For anything beyond a lab, use PostgreSQL or MySQL rather than SQLite, and put /var/lib/semaphore on storage you back up — it holds the database and cloned repositories. Confirm git --version and ansible-playbook --version work on the server or runner host before you start adding templates, because Semaphore shells out to both.

The resource model, in the order you build it

Resource What it is Order
Project Namespace owning everything else 1
Key store SSH keys, passwords, vault passwords (encrypted) 2
Repository Git URL plus credentials and branch 3
Inventory Static hosts, or a dynamic inventory script 4
Environment Extra variables, JSON, secrets injected at run time 5
Task template Playbook plus inventory, environment, and limits 6
Schedule Cron-style trigger for a template 7
Workflow Chain of templates with success/failure routing 8

Wiring an Ansible repository

# inside the Semaphore container/host, a template resolves to something like:
ansible-playbook -i inventory/hosts.ini playbooks/site.yml \
  --limit webservers -e @/tmp/semaphore/env.json --vault-password-file /tmp/semaphore/vault

# the UI fields that produce the above
#   Repository   -> git@github.com:example/infra-playbooks.git (branch: main)
#   Inventory    -> inventory/hosts.ini
#   Environment  -> extra vars JSON: {"env_name":"prod","reload_service":true}
#   Template     -> playbooks/site.yml, limit webservers

Keep secrets in the key store or in an environment variable marked as secret — never in the repository. If you use Ansible Vault, store the vault password as a Semaphore secret and reference it in the template; that way the password never lands on disk in plaintext for the duration of the run.

Schedules, workflows and runners

# schedule semantics (cron-like, with timezone from SEMAPHORE_SCHEDULE_TIMEZONE)
#   "0 2 * * 1-5"  -> weekdays at 02:00
# workflow: run "backup configs" then "rotate credentials" only on success
#   Template A -> (success) -> Template B
#   Template A -> (failure) -> Template C (notify / open ticket)

Schedules and workflows are the two features that turn a playbook runner into an operations platform: nightly configuration backups, a weekly compliance check, and a change that only continues if the pre-check passes. If the server is doing the work itself, tasks run locally; for scale, register remote runners so that execution happens near the managed hosts, and set MAX_TASK_DURATION_SEC and SEMAPHORE_MAX_PARALLEL_TASKS deliberately — the defaults assume a small environment, and a runaway parallel task count is a good way to offer a DDoS to your own infrastructure.

Operating it safely

Three habits. First, put Semaphore behind TLS and real authentication (LDAP/OIDC or a reverse proxy); it holds credentials for your whole estate. Second, restrict who can edit templates versus who can only run them — the API has role-based access, and "can edit the playbook that runs as root on 400 hosts" is not a permission to hand out casually. Third, keep the audit trail: every task run stores its full output, which is what makes a post-incident review possible when the question is "who reloaded the load balancer at 03:12".

Choosing a database and a backup that matches the stakes

Semaphore is a control plane: it holds credentials, the history of every change, and the definition of what runs where. That makes its database a recovery-critical asset, and the choice deserves more than the default.

Choice Suitable for Trade-off
SQLite (file) Labs and single-operator setups Simple backup, no concurrent writers, no HA
PostgreSQL Production, multi-user Needs a database to operate; standard backup tooling applies
MySQL Estates standardised on MySQL Same considerations as PostgreSQL

Whatever you choose, test the restore. A Semaphore instance restored from a database backup plus the data volume gives you back your templates, credentials and history; a restored instance without the data volume gives you back a shell that cannot clone the repository. Back up the volume and the database together, and put the reverse proxy configuration under version control with it — the certificate, the hostname and the proxy headers are part of the system, not incidental.

Secrets, environments and the discipline that keeps them secret

Semaphore stores credentials in an encrypted key store; the encryption protects the value in the database, not the value in the run. Anything a playbook needs lands in the container or the runner's temporary directory for the duration of the task, and that is unavoidable — so the discipline is about what you pass and how long it lives.

# environment variables (non-secret), stored per environment
{"env_name": "prod", "reload_service": true, "backup_dir": "/srv/backups"}

# secrets: define in the key store, reference by name; never in the repository
#   Key:          ssh-deploy-key
#   Type:         SSH
#   Vault pass:   ansible-vault-prod   (used as --vault-password-file)

# extra CLI flags a template can pass, kept explicit
--forks 20 --diff --check

Three rules pay for themselves. First, mark every variable that carries a secret as secret so it is masked in logs — task output is stored and searchable, and an unmasked token in the log is a credential leak with a long retention period. Second, scope credentials per environment rather than sharing one administrative key across staging and production, so a compromised staging project cannot touch production. Third, rotate periodically: Semaphore makes rotation cheap because the credential lives in one place, and the fact that it is cheap is the reason to actually do it.

Runners: where the work actually happens

A single-server installation runs tasks on the server itself, which is fine until the tasks are large, the server is in a different region from the managed hosts, or one team's heavy job starves another's. Remote runners solve all three: the server dispatches, the runner executes near the target, and capacity is added by registering another runner rather than by resizing the control plane.

# server-side limits worth setting explicitly
SEMAPHORE_MAX_PARALLEL_TASKS=8
MAX_TASK_DURATION_SEC=3600
SEMAPHORE_TMP_PATH=/var/tmp/semaphore
SEMAPHORE_GIT_CLIENT=cmd_git
SEMAPHORE_SCHEDULE_TIMEZONE=Asia/Shanghai

# runner registration is a token issued by the UI; the runner needs outbound
# access to the server, and network access to the hosts it configures

MAX_TASK_DURATION_SEC deserves a comment: without it, a playbook that hangs on an unreachable host holds a worker slot indefinitely, and a handful of those turns a healthy automation system into one where nothing runs. Set a ceiling per template and design tasks to fail fast — serial limits, a connection timeout and any_errors_fatal where appropriate.

Migrating existing playbooks without breaking habits

The migration path that works is incremental: keep the repository as the source of truth, add the Semaphore project alongside existing tooling, and move one workflow at a time. Start with something read-only — a facts-gathering or compliance-check playbook — so engineers see the value of the UI and the stored output before anyone is asked to trust it with a change. Then move the scheduled jobs, because those are the ones whose history people actually want. Move the emergency procedures last, after everyone has watched a normal change run through the tool and can see the log it leaves behind.

# a first template that cannot hurt anything
# Repository : infra-playbooks (branch main)
# Playbook   : playbooks/facts-gather.yml
# Inventory  : inventory/prod.ini
# Template   : name "Collect facts (read-only)", no limit
# Schedule   : "0 7 * * *" to build a daily baseline

FAQ

Semaphore or AWX? AWX has more enterprise features and a heavier footprint (Kubernetes operator, more moving parts). Semaphore is a single binary or container, which makes it easier to run on a management VM — the right choice when the goal is running playbooks reliably, not building a platform.

Does it replace Git? No. The repository remains the source of truth; Semaphore clones it per run and records which commit was used, which gives you the link between "who clicked run" and "what the code was at that moment".

Can it manage network devices as well as servers? Yes — playbooks are playbooks, and the network modules work as long as the runner can reach the devices. The credential store handles SSH keys and passwords uniformly, which is an improvement over the shell history most network teams start with.

What happens if the server is down? Scheduled tasks do not run and new jobs cannot start. Existing changes are not affected, which is why the server should be sized as a small but highly available service — a single VM with a backup and a documented restore procedure is usually sufficient.

Quick reference: install to first successful task

# 1) install (package or container), then initialise
semaphore setup --config ./config.json

# 2) sign in at http://localhost:3000 with the admin account you created

# 3) build the resource chain in the UI, in this order
#    Project -> Key Store -> Repository -> Inventory -> Environment -> Template

# 4) run the template and read the stored output; check the commit hash it reports
# 5) add a schedule once the template is proven, never before

The order matters because each resource depends on the previous one, and a half-built chain produces confusing errors: a template with no inventory fails with an Ansible error rather than a Semaphore one, and an environment variable typo looks like a playbook bug. Build the chain for a read-only playbook first, confirm the commit hash in the stored output matches the branch you expect, and only then point the tool at anything that changes production.

Related Reading

Related reading: Ansible AWX Job Templates and Workflow Configuration, Ansible Network Resource Modules: Declarative Config, Ansible for Network Automation: Complete Guide

原文链接:https://semaphoreui.com/docs/getting-started