Overview

Friday, 4:30 PM. The business chat starts flooding:

“@ops help me restart the order service” “@ops this endpoint is erroring, grab me the logs” “@ops is the disk full again? clear it for me”

Three requests, three @s. The ops engineer puts down the inspection script they were writing, SSHs in, runs commands, pastes screenshots back to chat, then gets asked “is it done yet?” An afternoon, shattered. This isn’t an edge case—it’s the default mode of ops work in most teams: high-frequency, low-difficulty, heavily manual.

I ran the numbers on an in-house ops platform project. After moving 20-odd operations (service restarts, cache clearing, config distribution, cert renewal, log retrieval, disk expansion…) from “@ someone in chat” into a self-service platform, the average per-operation time dropped from 15 minutes to about 90 seconds, and the ops team’s headcount spent on “responding to requests” fell by roughly 40%. No magic behind those two numbers—just one thing: turn operations into standardized actions on a platform.

This article lays out the complete thinking behind it: why platformization matters (not empty “efficiency” talk, but four concrete structural problems), how to design the 6-layer architecture, how to standardize the operation catalog, how to build approval flows that don’t get bypassed, and four real pitfalls I paid to learn. Code and configs are copy-ready.

One thing up front: operations platformization is not “a web UI that runs scripts.” If all you did was open a web terminal for developers to type commands themselves, that’s not platformization—it’s manual work in a different location. The core of platformization is that the operation itself becomes a structured asset: with parameters, permissions, approval, rollback, and audit. The web UI is just the front door to that system.

Four Structural Problems with Manual Ops

Before the architecture, let’s nail the problem. Many teams know manual ops is “slow,” but slowness is the symptom. What actually burns out ops teams are four structural issues:

Problem One: The request chain is too long; people’s time gets shredded

The full chain of one “help me restart a service” looks like this:

Business @s ops in chat (waiting for response: 5 min)
  → Ops confirms blast radius (dig through CMDB / ask business: 5 min)
  → Ops SSHes in and executes (1 min)
  → Ops confirms in chat + pastes result (2 min)
  → Business asks follow-up verification (3 min)

15 minutes per incident, of which the actual “executing the operation” is 1 minute. The other 14 are communication overhead. Red Hat shared a real datapoint in an AIOps writeup: a UK financial firm runs 600+ Ansible Automation Platform tasks per week (provisioning, patching, compliance scans, config drift fixes), generating about 40 failure tickets weekly—without a platform, each of those 40 tickets is one “@ ops in chat.” At that volume, a manual-response model inevitably collapses.

Problem Two: The audit black hole

The biggest risk of manual execution isn’t slowness—it’s that you can’t account for it. During a postmortem, the answers to these questions are often “don’t know”:

  • Who logged into that box at 11 PM last night?
  • What commands did they run? How many succeeded?
  • What did that config file look like before it was changed?

SSH-direct operations leave audit trails only in ~/.bash_history (which can be wiped) and bastion recordings (if you have one). Level-of-Protection 2.0 audits demand operation logging and traceability—manual mode basically can’t pass. I felt this acutely during a compliance project: the auditor’s first question is always “where are the operation logs,” and manual ops has no answer for that.

Problem Three: The knowledge gap

“Ask Zhang to restart the order service, ask Li to clear the cache”—operational knowledge lives in specific people, not in the system. Zhang goes on vacation, and nobody dares touch the order service. Every operation depends on a specific person, a specific path, a specific sequence, with no documentation—or documentation that no longer matches reality.

Problem Four: Uncontrolled risk

Manual execution has no foolproofing. Pasting into the wrong environment, rm -rf missing a dot, running a command validated in test straight against production—these have all happened. Google’s SRE Book has a dedicated concept for this kind of repetitive manual work: toil—manual, repetitive, automatable, devoid of long-term value, scaling linearly with service size. Toil isn’t just an efficiency problem; it’s a risk problem—human fatigue and complacency in repetitive operations is itself a fault source (the full treatment of this concept is in related article: Eliminating Toil).

Summary comparison:

ProblemManual modePlatform mode
Response chain@ person in chat, 15 min/ops, 90% commsSelf-service, 90 sec/ops, unattended
Audit trailRelies on history and memory, guessing at postmortemsFull operation logs + before/after snapshots
Knowledge retentionLives in people, breaks when they leaveOperation catalog is the docs, newcomers execute by list
Risk controlRelies on diligence and care, no foolproofingPermission interception + param validation + high-risk double-review

What Operations Platformization Actually Is

In plain terms: operations platformization turns “who, on which machine, executes what operation, with which parameters” from a verbal contract between people into a structured contract between systems.

Four elements unpacked:

  1. Who — the operator has an identity; the permission system decides whether they can act
  2. On which machine — the target is a managed resource, not just any IP
  3. What operation — the operation itself is a predefined standard action, not an ad-hoc command
  4. Which parameters — parameters have types, validation, defaults; dangerous values get intercepted

A boundary worth clarifying: automation scripts solve “how to execute”; the platform solves “who, when, whether permitted, whether rollbackable, how audited.” You write an Ansible playbook to restart a service—that’s automation. You mount that playbook on a platform where a business user fills in parameters, clicks a button, waits for approval, sees the result, and leaves an audit trail—that’s platformization. The two are progressive, not substitutive.

The evolution usually looks like:

Phase 0: Pure manual (SSH + memory)
  ↓ Scripting: write common ops as scripts (solves "how to execute")
Phase 1: Script repository
  ↓ Scheduling: cron / systemd timer for fixed tasks (solves "when to execute")
Phase 2: Scheduled tasks
  ↓ Web-ify: expose scripts via web UI (solves "who can execute")
Phase 3: Ops web-ified
  ↓ Platformize: add permissions, approval, audit, rollback (solves "permitted, rollbackable, audited")
Phase 4: Self-service: business users self-initiate, ops only approves and backs up

Many teams get stuck between phases 2 and 3: they’ve written a pile of scripts, but the entry point is still “ops, please run this for me.” The reason usually isn’t technical—it’s that nobody built the governance layer of permissions, approval, and audit. The architecture below solves exactly that.

Six-Layer Architecture Design

The full picture first, then layer by layer:

┌─────────────────────────────────────────────────────┐
│  L1 Entry      Web console │ ChatOps │ OpenAPI │ cron  │
├─────────────────────────────────────────────────────┤
│  L2 Governance RBAC │ approval workflow │ high-risk review │
├─────────────────────────────────────────────────────┤
│  L3 Catalog    Operation Catalog (structured op defs)  │
├─────────────────────────────────────────────────────┤
│  L4 Orchestration DAG │ concurrency control │ retry     │
├─────────────────────────────────────────────────────┤
│  L5 Execution  Ansible │ raw SSH │ K8s API │ cloud API   │
├─────────────────────────────────────────────────────┤
│  L6 Audit      Op logs │ session replay │ config diff   │
└─────────────────────────────────────────────────────┘

L1 Entry Layer: where operations originate

The entry layer answers “where does the user initiate from.” Three entries, uneven importance:

  • Web console (required): full operation catalog browsing, parameter forms, approval flow, execution history. The main entry.
  • ChatOps (recommended): type /restart order-service --env prod right in IM to fire an op. Key to lowering the barrier—business folks don’t want to open a webpage just to restart a service. The IM bot forwards the command to the platform API, reusing the same permission and approval logic.
  • OpenAPI (advanced): for CI/CD pipelines and other systems to call. E.g., the deploy system auto-invokes a “warm cache” operation after deployment.

My take: entries can vary, but they must converge on one permission and audit logic. I’ve seen the anti-pattern: a ChatOps bot bypassed approval “for speed,” and when something went wrong, the audit trail had nothing but an IM message.

L2 Governance Layer: who can do what

This is the soul of platformization; I dedicate a chapter to the permission model and approval flow below.

L3 Operation Catalog: the platform’s core asset

The operation catalog is the registry of all platformized operations. Each operation is a structured definition: name, description, parameter schema, execution body, risk level, rollback method. This layer is the platform’s core asset—the quality of the operation catalog directly determines the platform’s vitality. Expanded below.

L4 Orchestration Engine: what about multi-step ops

Single-step ops (restart one service) execute directly, but real-world ops are often multi-step: scale-out = create new instance + attach to load balancer + health check + notify. This needs an orchestration engine supporting:

  • Sequential dependency: step B depends on step A’s output
  • Parallel branches: independent steps run concurrently to cut total time
  • Conditional jumps: pass check → branch A, fail → branch B
  • Failure strategy: on single-step failure—abort, retry, or skip

The orchestration engine can be modeled directly as a DAG (directed acyclic graph). The full design of a DAG scheduler (topological sort, state machine, concurrency control) is in related article: Building a Go DAG Scheduler Engine; not repeated here.

L5 Execution Channel: where the real work happens

The execution channel is the “hands and feet.” By target type, four kinds:

ChannelTargetTypical tool
Ansibletraditional hosts, batch opsansible-playbook
Raw SSHsingle-host quick opsgolang.org/x/crypto/ssh
K8s APIcontainerized workloadsclient-go
Cloud APIcloud resourcesaliyun-sdk / tencentcloud-sdk

My recommendation: route host-type ops through Ansible uniformly; don’t maintain your own SSH connection pool. The reasoning is practical: Ansible’s Inventory management, batch concurrency, and module ecosystem (copy, service, systemd) are all ready-made. Rolling your own means handling connection reuse, timeout control, output parsing—a pile of grunt work. A Go platform can call playbooks in-code via github.com/apenella/go-ansible; best of both worlds.

The raw SSH channel keeps one purpose only: the live terminal (a manual-login fallback for users), and it must be behind a bastion with recording.

L6 Audit Layer: explainable when things go wrong

The audit layer records four kinds of data:

  1. Operation logs: who, when, what op, what params, what result
  2. Session replay: full playback of web terminal and SSH sessions (record tty output via ascinema)
  3. Config snapshots: before/after diff of change-type operations
  4. Rollback plans: the preset rollback action for each high-risk operation

The audit query interface must serve the postmortem scenario: filter by host, by time window, by operator. Audit that’s recorded but not queryable equals not recorded—a lesson I expand on in the pitfalls chapter.

Operation Catalog: Turning Operations into Structured Assets

This chapter is the heart of the article. I’ll explain the operation catalog design with an actual YAML definition (the real format we use, simplified for presentation):

# operation-restart-service.yaml
# One record in the operation catalog: defines the "restart service" operation
apiVersion: ops/v1
kind: Operation
metadata:
  name: restart-service          # unique op id, referenced in code
  displayName: "Restart specified service"
  category: service              # service / host / config / cert...
  riskLevel: medium              # risk level: low / medium / high
  owner: sre-team                # op owner (approval routes here by default)
spec:
  description: |
    Restart the specified service in the specified environment.
    Rolling restart only; automatically waits for health checks to pass.    
  params:                        # param defs: auto-rendered form + backend validation
    - name: service
      type: enum
      required: true
      options: [order-service, pay-service, user-service]
      description: "Service to restart"
    - name: env
      type: enum
      required: true
      options: [test, staging, prod]
      description: "Target environment"
    - name: reason
      type: string
      required: true
      maxLength: 200
      description: "Reason for the op, written to audit log"
  execution:
    type: ansible                # channel: ansible / ssh / k8s / cloud
    playbook: playbooks/service/restart.yaml
    timeout: 300                 # timeout in seconds, prevents hung tasks
  approval:
    prod: [owner-confirm]        # prod requires owner confirmation
    staging: []                  # staging: no approval
    test: []                     # test: no approval
  rollback:                      # rollback plan: restart is idempotent, rollback = re-run
    type: re-execute

Several key designs in this definition, each added only after a painful lesson:

Design one: parameters must be enum-first, reject free text. The service param uses enum not string; users pick from a dropdown and can’t typo a service name. Free-text params are a source of incidents—real case: the env param should’ve been prod, the business user fat-fingered pord, and the script took an anomalous branch on the wrong judgment. If it can be an enum, never a string; if it can be a dropdown, never free input.

Design two: reason is mandatory and goes to audit. This field seems redundant, but it’s gold during postmortems—“why did someone restart the payment service at 11 PM last night” is answered right in the operation record, no need to dig through chat logs.

Design three: approval policy is tiered by environment. The same operation: test env no approval (encourage self-service), prod env requires confirmation (hold the line). Tiering is the universal principle of platformization; one-size-fits-all will backfire.

Design four: riskLevel drives approval intensity. See the permission model chapter.

Here’s a complete definition for a high-risk operation—focus on the double-review and rollback plan:

# operation-disk-expand.yaml
# High-risk example: production disk expansion
# Risk: involves data volumes; failure can corrupt the filesystem
apiVersion: ops/v1
kind: Operation
metadata:
  name: disk-expand
  displayName: "Expand data disk"
  category: host
  riskLevel: high               # high-risk: triggers double review + change window
  owner: sre-team
spec:
  description: |
    Expand the data disk on the specified host and grow the filesystem.
    Online expansion only (LVM scenarios); shrink is not supported.    
  params:
    - name: host
      type: enum
      required: true
      options: [db-01, db-02, cache-01, cache-02]
      description: "Target host (whitelist only)"
    - name: device
      type: enum
      required: true
      options: [/dev/vdb, /dev/vdc]
      description: "Target disk device"
    - name: sizeGB
      type: integer
      required: true
      min: 100                   # range validation: prevent 10GB or 100000GB typos
      max: 2000
      description: "Target capacity after expansion (GB)"
  execution:
    type: ansible
    playbook: playbooks/host/disk-expand.yaml
    timeout: 600
    idempotent: true             # expansion to target size is naturally idempotent
  approval:
    prod:
      - owner-confirm            # gate 1: owner confirms
      - double-review            # gate 2: double-review code confirmation
      - change-window            # gate 3: must be within change window
    test: [owner-confirm]
  rollback:                      # rollback plan: mandatory for high-risk ops
    type: manual                  # disk shrink is unsafe; manual assessment only
    runbook: runbooks/disk-expand-rollback.md

Compared to the medium-risk restart op, the high-risk definition adds three things:

  • double-review approval node: the second person’s confirmation code check (implementation in the next chapter)
  • change-window constraint: only allowed within a change window (e.g., weekdays after 22:00); submissions outside the window are rejected with a stated reason
  • rollback.runbook: the rollback plan isn’t “re-run,” but a link to a manual playbook—for data-safety-critical ops like disks, automated rollback carries more risk than the forward operation. Admitting this is more responsible than faking an auto-rollback.

Idempotency: the prerequisite for repeatable execution

Every operation in the catalog must answer one question: is executing twice the same as executing once? That’s idempotency. It’s the biggest dividing line between platformized operations and manual scripts.

Why it matters: in a platform environment, operations get retried—frontend resends after a network blip, the orchestration engine retries on failure, the user clicks again when nothing seems to happen. If the operation isn’t idempotent, every retry is an incident.

Anti-example (non-idempotent):

# Wrong: a script that clears the cache directory
# On second run, it deletes cache data generated after the first run
rm -rf /data/cache/*
echo "cache cleared"

Correct example (idempotent):

# Correct: delete only files older than 7 days
# Running multiple times yields the same result (old files already gone)
find /data/cache -type f -mtime +7 -delete
echo "cache cleared, removed files older than 7d"

Most Ansible modules are naturally idempotent (the service module starting an already-started service is a no-op on the second run)—one more reason I recommend Ansible as the primary channel. When writing your own scripts, idempotency is a mandatory check in code review.

Permission Model and Approval Flow: Governance That Doesn’t Get Bypassed

This chapter covers L2. Conclusion first, then argument:

The goal of the approval flow isn’t to “block operations,” but to make compliant operations faster than bypassing the platform.

This is what I learned the hard way. The first version of our platform had strict approval: all prod operations required a three-level chain (requester → ops lead → tech director). Three months later, bastion logs showed SSH-direct operations had gone up—users were voting with their feet past the platform. An approval flow that’s too heavy gets bypassed by the manual channel.

Permission Model: a three-dimensional matrix

RBAC in an ops platform materializes as a three-dimensional matrix: user × operation × environment.

# Access policy example: an order-team developer's permissions
# Read as "who can do what operation in which environment"
apiVersion: ops/v1
kind: AccessPolicy
metadata:
  name: order-team-dev
subjects:                        # who is authorized
  - group: order-team-dev        # order team dev group
operations:                      # which operations
  - restart-service              # restart service
  - tail-service-log             # tail logs
  - clean-cache                  # clear cache
environments:                    # in which environments
  - test
  - staging
effect: allow                    # allow / deny

In this model, the environment dimension is key. For most teams, the permission problem isn’t “can you run this operation,” but “can you run it in prod.” Making environment a first-class citizen in the permission matrix—test open for self-service, prod locked behind approval—preserves efficiency while controlling risk.

Operation Risk Tiers: different risk, different intensity

Not all operations are created equal. Three tiers, three control intensities:

RiskCharacteristicsTypical opsControl
Lowread-only, reversible, small blast radiustail logs, check status, view configno approval, fully self-service
Mediumreversible, single-service impactrestart service, clear cache, scale upone-level approval (op owner)
Highirreversible, global impact, data-involveddelete data, kernel params, cert replace, network changedouble review + change window

Double-review for high-risk ops must be real: after the requester submits, the system generates a confirmation code that a second person must enter on the page to proceed. The code isn’t a captcha (machine-recognizable); it’s a string that has to be read aloud to the other person—forcing a real human conversation. Sounds tedious, but for data-deletion ops, that extra 30 seconds of communication has saved us at least once.

A few nuances in the review-code implementation, straight to the core logic:

// IssueReviewToken generates a double-review code for a high-risk operation
// Design: short TTL + operation-bound + single-use
func IssueReviewToken(opExec *OperationExecution) (string, error) {
    // The code binds the operation ID and parameter digest—
    // when the reviewer reads the code aloud, the requester's screen
    // shows the same operation and the same parameters.
    // Prevents a timing mismatch of "requester submits op A,
    // reviewer approves op B."
    payload := fmt.Sprintf("%s|%s|%d",
        opExec.OperationName, opExec.ParamsDigest(), opExec.ID)
    token := hotp.Generate(payload, 6)   // 6 digits, easy to read aloud
    store.SetWithTTL("review:"+payload, token, 10*time.Minute)
    return token, nil
}

// VerifyReviewToken second person enters the code to release
func VerifyReviewToken(opExec *OperationExecution, input string) error {
    payload := fmt.Sprintf("%s|%s|%d",
        opExec.OperationName, opExec.ParamsDigest(), opExec.ID)
    stored, ok := store.GetAndDelete("review:" + payload)  // get-then-delete
    if !ok {
        return errors.New("review code expired, please resubmit")
    }
    if stored != input {
        return errors.New("review code mismatch")
    }
    return nil
}

Two details worth a second look:

  • The code binds the parameter digest. If the requester changes params during review (e.g., disk expansion from 500GB to 800GB), the old code auto-invalidates and a fresh review is required. Parameters and the confirmation action are locked together, eliminating “confirmed A, executed B.”
  • Get-then-delete. The same code can’t be used for two executions—otherwise the second high-risk operation bypasses the second person’s confirmation.

The cost is 30 seconds of communication per high-risk op; the payoff is eliminating the most finger-pointing failure mode (“I thought they were doing it”) at the mechanism level.

Approval Flow Engineering Essentials

The approval workflow itself isn’t complex (state machine: pending → approved/rejected → executing → done/failed), but three engineering details make or break it:

Detail one: approvals must reach asynchronously. Push approval requests to the approver’s IM, with an operation summary (who, what op, what env, what reason), one-click approve or reject. No need for the approver to log in and hunt for red dots. After we wired in the IM bot, average approval response time dropped from 2 hours to 8 minutes.

Detail two: approvals need timeout escalation. If the approver doesn’t respond in 30 minutes, auto-escalate to their superior or a backup approver. Otherwise “approver on vacation” becomes the perfect excuse to bypass the platform.

Detail three: every approval action itself goes to audit. Who approved, when, the parameter snapshot at approval time. When something breaks, the chain of responsibility is clear.

ChatOps Entry: letting ops be “fired on a whim”

After the approval flow is in place, the next question is entry reachability. The web console is the main entry, but in reality business folks don’t want to open a browser, find the catalog, fill a form just to restart a service. ChatOps isn’t a toy; it’s the key to whether the platform actually gets used.

The IM bot forwards commands to the platform; the core is command parsing → permission reuse → audit unification, all through one logic path. The command format should be restrained—not a scripting language:

/restart order-service --env prod --reason "suspected memory leak recurrence"

Parsing pseudocode:

// parseChatOpsCommand parses an IM bot command, reuses the web entry's
// permission and audit. Format: /opname args --flag value
func parseChatOpsCommand(text string) (*OperationRequest, error) {
    fields := strings.Fields(text)
    opName := strings.TrimPrefix(fields[0], "/")   // strip / prefix, get op name
    op, err := catalog.Get(opName)                  // look up definition in catalog
    if err != nil {
        return nil, fmt.Errorf("operation %q not found, use /list for available ops", opName)
    }
    req := &OperationRequest{Operation: opName}
    // remaining fields parsed per the op's param schema (enum/range validation)
    if err := op.BindParams(fields[1:], req); err != nil {
        return nil, err                              // validation failure returns, no approval flow
    }
    return req, nil
}

Three engineering points:

  • Operation name and param schema reuse the operation catalog directly. No parallel definition in ChatOps. The request parsed from /restart and the request submitted via web form are the same OperationRequest struct; downstream permission checks, approval flow, and audit run identical code paths
  • Permission reuse: the IM account maps to a platform user (binding established on first use); RBAC checks match the web side. No permission gap of “usable in IM, not on web”
  • Audit unification: ops initiated via ChatOps are tagged with entry chatops in the audit record, queryable in the same table as web-initiated records

I’ve seen the anti-pattern: a team’s IM bot executed commands directly, skipping approval, for speed. Audit had only an IM message. When an env config got corrupted, no one could say what it looked like before. This is the classic crash of “diverse entries, fragmented governance”—entries can be many; governance logic can be one.

Landing Path: Four Phases, Each with Acceptance Criteria

Platformization can’t be swallowed whole. My experience: four phases, each with clear acceptance criteria—don’t advance until the bar is met.

Phase One: Script Standardization (laying the foundation)

Gather the team’s existing high-frequency scripts and unify the standard. Acceptance criteria:

  • Top 10 high-frequency ops all scripted, each with: parameter docs, idempotency guarantee, explicit exit codes, structured log output
  • Scripts in one Git repo, banned from personal directories
  • Each script run at least once in a test environment

No platform in this phase, only standards. It seems unglamorous, but without standardized scripts, everything after is castles in the air—garbage scripts platformized become automated garbage production.

Common blocker: “no time to clean up scripts.” Handle it by pulling the Top 10 from ticket data and standardizing only those—the most-@-ed operations are the most worth platformizing first. Don’t try to consolidate all legacy at once; let the backlog die off through usage.

Phase Two: Operation Catalog-ization (put Top 10 on the platform)

Define the Top 10 operations in catalog format, mount the web UI. Acceptance criteria:

  • 10 operations launchable and executing correctly from the web
  • Each operation has parameter form validation
  • Execution history queryable (audit can be basic here, but can’t be absent)

The goal of this phase is to let the team taste the payoff. Tail-logs going from “@ ops wait 15 min” to “click 30 sec yourself” generates positive feedback that pushes platformization forward.

Common blocker: over-engineering the operation definitions. Trying to make parameters universal from day one (regex, expressions) yields a form so complex no one uses it. The principle: make the 80% common case foolproof dropdowns first; let the 20% long tail go through tickets—over-flexible entry equals no entry.

Phase Three: Permissions and Approval (set the rules)

Wire in RBAC and the approval flow; high-risk double-review goes live. Acceptance criteria:

  • All operations have permission control; unauthorized attempts intercepted and logged
  • 100% of prod operations go through the approval flow
  • Audit logs cover all operations, searchable by person, time, host

This phase faces the most resistance—business folks complain “I used to just @ and it got done, now there’s approval.” Counter with data: put “approval time” next to “old @-and-wait time.” Our measured one-level approval averaged 8 minutes, while old chat-@ response averaged 15+ minutes. Approval didn’t slow things down; it turned response time into an SLA’d commitment.

Phase Four: Self-service Opening (harvest)

Open most test-environment operations to business self-service; ops keeps only prod and high-risk approval. Acceptance criteria:

  • 80%+ of test-env operations done by business self-service
  • Ops “respond to request” ticket volume down 50%+
  • Platform MAU covers 60%+ of developers

At this point, the daily “@ ops in chat” truly becomes the daily “discussing architecture in chat.”

Common blocker: self-service scope creep. Enthusiasm for opening up leads to exposing things that shouldn’t be (like an arbitrary-SQL-on-prod entry). Set red lines ahead of time: data-write operations, cross-global network changes, irreversible deletes—never enter the self-service zone, no matter the complaints.

The Platform Itself Can’t Be a Single Point: HA and Degradation Design

A hidden risk of platformization is easy to miss: if the ops platform goes down, all operations stall, and the ops team instantly degrades to something worse than “manual ops”—because the process has grown dependent on the platform, and the manual channel has gone rusty. The platform’s own HA isn’t optional; it’s part of platformization.

Three lines of defense:

Line one: stateless execution layer. Scale execution nodes (the machines running Ansible) to at least two. Ansible control nodes are stateless (Inventory and playbooks on shared storage or distributed with the node); the platform side does health checks and fault removal. The execution layer is the easiest to scale—no excuse for a single point.

Line two: control-plane primary-replica. Web UI and API services run dual instances; database primary-replica (losing one operation record is an audit loss—DB HA can’t be skimped). Half the control plane down doesn’t affect in-flight ops—running tasks continue on execution nodes and write results with delayed persistence.

Line three: degradation plan. When the platform is fully down, degrade to “manual ops with approval”: approval via IM chat (on-call manager verbal approval), ops via bastion (always-on recording), audit back-filled within 24 hours. This plan goes into the on-call handbook and is drilled quarterly—a degradation plan that’s never been drilled doesn’t exist; when it hits for real, no one remembers the process, no one has permissions, no one dares act.

We added this layer only after eating the loss (detail in pitfall three). Cost isn’t high: two execution nodes plus a primary-replica DB is under 10% of the overall platformization investment.

Production Pitfalls Log

Four real pitfalls, each paid for in tuition.

Pitfall One: Approval too heavy, users vote with their feet

Mentioned above; let me expand. When the first version launched, I designed three-level approval (requester → ops lead → tech director), intending safety first. Three months later, a platform-usage review showed two glaring numbers: MAU was 40 (out of a 200-person dev org), and bastion SSH logins had gone up.

Talking to business, the feedback was blunt: “To restart a test-env service and wait for three leads to approve—the food’s gone cold. I’d rather just SSH.”

The fix was tiering + speedup:

  • Test-env operations all approval-free (self-service)
  • Prod low-risk ops one-level approval (op owner, IM one-click)
  • Only high-risk ops keep double review
  • Approval auto-escalates after 30-min timeout, no longer blocked on one person

A month later, MAU went from 40 to 130. Governance intensity should match risk, not imagined fear.

Pitfall Two: Non-idempotent script, retry becomes incident

The earliest cache-clear script was rm -rf /data/cache/*. One day, a network blip meant the frontend got no response; the business user refreshed and clicked again. The two executions were 40 seconds apart; on the second run, cache data generated after the first clear (including a batch of in-flight session files) was deleted along with everything else, causing 10 minutes of login anomalies.

The fix had three layers:

  1. Script changed to find /data/cache -type f -mmin +10 -delete (delete only files older than 10 minutes)—idempotent
  2. Platform frontend added anti-duplicate-submit (button disabled after click until response)
  3. Operation catalog definition gained execution.idempotent: true; the orchestration engine disables auto-retry for non-idempotent ops

This pitfall made “idempotency is a mandatory field in the operation catalog” a platform standard.

Pitfall Three: Execution channel single point, the platform itself became a fault source

The Ansible execution node was initially deployed on one machine. One day that machine’s disk was filled by logs, and all batch operations hung—the ops platform itself became the biggest single point of failure. That day felt like dark humor: we built HA for all the business, and the platform went down first.

The fix:

  • Execution nodes made stateless, horizontally scaled to two (low cost; Ansible control nodes are stateless; Inventory on shared storage)
  • Platform-side execution-node health checks, faulty nodes removed
  • Fallback plan: the bastion manual channel is always retained; when the platform is fully down, degrade to “manual ops with approval” (approval via IM, audit back-filled post-hoc)

Pitfall Four: Audit recorded but not queryable, equals not recorded

The early version’s audit log was just inserting records into the database—no query UI. During a security audit (Level-of-Protection 2.0 check), the auditor wanted “everyone who executed a config change in prod in the past month.” We hand-wrote SQL for half an hour, and the format didn’t even meet requirements.

The subsequent overhaul:

  • Audit log gained a three-dimensional search UI (person / time window / host)
  • Added “operation playback”: click an execution record to see the full output (Ansible execution stdout persisted to disk)
  • Change-type ops auto-generate config diff snapshots

The audit boundary is “when something breaks, quickly reconstruct the facts.” Anything short of that is self-comfort.

Measuring Outcomes: What Did Platformization Actually Buy

Empty “efficiency gains” aren’t convincing. Here’s the measured data after running 20+ operations on the platform for half a year (baseline: the three-month pre-platform manual-ops average):

MetricPre-platformPost-platformChange
Avg per-op time~15 min~90 sec↓ 90%
Of which ops headcount15 min2 min (approval + exception handling)↓ 87%
Ops “respond to request” tickets~120/mo~45/mo↓ 62%
Audit coverageunmeasurable100% (in-platform ops)0 → 1
Prod change-related incidents5 in 6 mo before2 in 6 mo after↓ 60%

A few notes:

  • “Per-op time 90 sec” includes: requester filling the form 30s, approval wait 30s (IM one-click), execution 30s
  • Ticket volume dropped 62% not 80%, because the remaining 45 are mostly complex ops the platform can’t cover (tricky cases needing human judgment)—which is exactly what ops should spend time on
  • Change incidents down 60%, mainly from parameter validation (enums eliminate env typos) and idempotency (retries no longer amplify incidents)

One number to be honest about: the 40% headcount saving is on “responding to operation requests,” not the ops team’s total headcount. Where did the freed time go? Partly to inspection-platform development (automating inspection too—see related article: Plugin Architecture for an Automated Inspection Platform), partly to capacity planning and disaster-recovery drills. Less toil, more engineering time—that’s the positive loop of toil reduction.

Summary

The key points condensed into one paragraph:

Operations platformization turns “who, on which machine, executes what operation, with which parameters” from a verbal contract into a system contract. It solves not just efficiency (though 15 min → 90 sec per op is substantial), but the three structural problems manual mode can’t solve at all: audit, knowledge retention, and risk control.

Four lessons I most want to leave you:

  1. The operation catalog is the core asset, more important than the orchestration engine or the UI. The YAML-defined operation standard is platform-agnostic; nail the format first.
  2. The approval flow’s goal is to make compliant ops faster than bypassing, not to block operations. Tiered control (low-risk self-service, medium one-level approval, high-risk double review), with IM-reachable approvals and timeout escalation.
  3. Idempotency is the admission ticket for an operation to enter the catalog. Non-idempotent operations in a platform environment are ticking bombs; retry mechanisms detonate them.
  4. Audit that can’t “quickly reconstruct facts when something breaks” is as good as none. Three-dimensional search + execution playback + config diff as a starting trio.

On selection, remember one rule: under 50 machines, start with Spug; heavy Ansible users look at AWX first; with dedicated ops-dev headcount, consider building in-house. Platformization is a marathon—in year one, achieving “all high-frequency ops self-service, 100% of prod ops audited” already beats most teams.

References & Acknowledgments

The following materials were referenced during the writing of this article. Thanks to the original authors for their contributions:

  1. Spug - Open Source Automation Ops Platform — openspug community, architecture and feature design reference for the lightweight agentless ops platform
  2. How can SMBs do ops automation? — Spug official blog, platform feature list and applicable-scenario analysis
  3. Deep Review of Four Major Automation Ops Systems in 2026 — trainsignalcn, industry practice on the three-layer architecture (process/platform/scenario) and high-risk operation tiered review
  4. Demystifying agentic AI: How to build production-ready AIOps with open source models — Red Hat, production data reference: a UK financial firm’s 600+ weekly Ansible tasks and 40 weekly failure tickets
  5. Google SRE Book - Eliminating Toil — Google SRE team, the definition and governance framework for toil