8 minute read

Updated:

Cloudera has a lot of ways to get a cluster. Almost none of them are “one command on your laptop.” cloudera-labs/cloudera-ce-aws is the exception: a Terraform + Ansible bundle that stands up a full Cloudera Private Cloud Community Edition cluster on AWS — Cloudera Manager, Kerberos, Auto-TLS, a real storage/compute topology — from a single ansible-navigator run. This post is me taking my freshly-released fork from zero to a running Ozone cluster, and the handful of real snags between the README and a green Cloudera Manager.

Everything here is field-run against cloudera-ce-aws v1.0.0, deploying Cloudera Manager 7.13.2 / Runtime 7.3.2, from an Apple-Silicon Mac into AWS account AWS SE in us-east-2.


What it actually deploys

A ring-fenced cluster — ~11 EC2 nodes — with everything a real Cloudera deployment has and a laptop demo usually fakes:

  • Cloudera Manager with Kerberos and Auto-TLS
  • Self-contained DNS, Kerberos (FreeIPA), PostgreSQL, and TLS (ACME-managed certs on a Caddy reverse proxy)
  • A selectable topology — Ozone, Kafka, Flink, NiFi, CSA, or ECS — each its own playbook
  • Reverse HTTPS proxies + SSH as the only ways in; the cluster is otherwise sealed

The whole thing is idempotent: re-running produces no unintended changes. The node roles and default sizing (t3a for most, one r5a.xlarge for CMS):

Role Count Instance
gateway 1 t3a.medium
services 1 t3a.large
masters 3 t3a.xlarge
workers 4 t3a.xlarge
cms 1 r5a.xlarge
sdx 1 t3a.xlarge

At on-demand rates that’s roughly ~$2/hr / ~$45/day — worth a pause.yml between sessions (stops EC2, keeps EBS) if you’re leaving it up.

AWS EC2 console — the ~11 EC2 instances the deployment provisions, one per cluster role (gateway, services, masters, workers, cms, sdx)


The setup is genuinely minimal

Every dependency — Terraform, Ansible, all the collections — is baked into an Ansible execution-environment container image. Locally you need almost nothing:

git clone https://github.com/cloudera-labs/cloudera-ce-aws.git
cd cloudera-ce-aws
python -m venv ~/cdp-navigator && source ~/cdp-navigator/bin/activate
pip install ansible-core ansible-navigator

Plus a container runtime (Docker or Podman) and two credentials: AWS SSO and a Cloudera license .txt.

# AWS SSO — the config uses your SSO profile to mint short-lived creds
aws sso login --profile YOUR_PROFILE

# Cloudera Private Cloud license — the text file, NOT the .zip
export CDP_LICENSE_FILE=/path/to/license.txt

Then a three-line config.yml:

name_prefix: "steven-ce"
infra_region: "us-east-2"
common_password: "<min 8 chars, 1 number>"
owner_email: "you@cloudera.com"

And the one command that does everything:

ansible-navigator run playbooks/infrastructure.yml playbooks/services.yml \
  playbooks/cms.yml playbooks/ozone-cluster.yml -e @config.yml -m stdout

Four playbooks, four stages: Terraform provisions the AWS infra → Ansible configures DNS/Kerberos/DB/TLS → Cloudera Manager comes up → the Ozone cluster deploys.


The snags between README and a running cluster

The quickstart is clean, but seven things cost me time — exactly the stuff a reveal post should call out. The first four are one-time setup friction; the last three are genuine traps in the v1.0.0 release.

1. The :latest EE image tag isn’t published

ansible-navigator.yml points the execution environment at ghcr.io/cloudera-labs/cloudera-ce-aws:latest. That tag doesn’t exist — the registry only publishes 1.0.0-amd64:

Error response from daemon: failed to resolve reference
"ghcr.io/cloudera-labs/cloudera-ce-aws:latest": not found

Fix — pin the real tag in ansible-navigator.yml:

    image: ghcr.io/cloudera-labs/cloudera-ce-aws:1.0.0-amd64

2. The EE image is amd64-only — on Apple Silicon it runs emulated

The only published arch is -amd64. On an M-series Mac the image runs under emulation; make it explicit so Docker doesn’t guess:

    container-options:
      - "--network=host"
      - "--platform=linux/amd64"

The EE is an orchestration controller — it drives Terraform and SSHes to the nodes; it isn’t doing heavy local compute — so emulation is a non-issue for throughput here.

3. “Logged into AWS” (console) ≠ AWS CLI has credentials

I was logged into the AWS access portal in the browser, but the CLI had no profile, no cached token, nothingNoCredentials. The fix is aws configure sso, but the trap is subtler: after setup my default profile carried the sso_session but was missing sso_account_id and sso_role_name, so it still couldn’t resolve credentials. A complete profile:

[sso-session Cloudera-Main-SSO]
sso_start_url = https://d-xxxxxxxxxx.awsapps.com/start#/
sso_region = us-east-1
sso_registration_scopes = sso:account:access

[profile cldr-se]
sso_session = Cloudera-Main-SSO
sso_account_id = 007856030109
sso_role_name = cldr_poweruser
region = us-east-2

aws sts get-caller-identity --profile cldr-se should return your assumed-role ARN. That token is cached on disk, so it survives across shells — which matters because the deploy consumes the creds via aws configure export-credentials.

4. common_password and config.yml are secrets in a public repo

config.yml holds a plaintext password and your fork is public. It’s in .gitignore (alongside *.pem and *.tfstate) — confirm that before you commit anything, because a leaked common_password there unlocks every service in the cluster.

5. Keep common_password alphanumeric — special characters break service enrollment

:bulb: Hint — this one cost me a full teardown. Make common_password letters and digits only. Cloudera’s automation sets service admin passwords through basic-auth API calls shaped like https://admin:PASSWORD@host/..., so an @ or # inside the password corrupts the URL’s userinfo section and enrollment fails — and the task is no_log, so the error is censored and you can’t see why. Alphanumeric still satisfies the “min 8 chars, 1 number” rule; you lose nothing.

My first run had common_password full of special characters (#, @). The deploy sailed through Terraform and most of the services stage, then died on:

TASK [cloudera.exe.grafana : Set Grafana admin password if API login fails]
fatal: [<services-node>]: FAILED! => {"censored": "... 'no_log: true' ..."}

The result is censored (no_log), but the cause is the password: common_password feeds service admin passwords that get set via basic-auth API calls (https://admin:PASSWORD@host/...). An @ inside the password breaks URL userinfo parsing, so the API login/set fails. The same password later feeds CM, Ranger, Knox, Hue, and SMM — so this isn’t a Grafana quirk, it’s a landmine for every API-set credential downstream.

Fix: keep common_password alphanumeric (letters + digits, meets the “min 8, 1 number” rule without @ # $ / :). Because the password is baked into FreeIPA/DB/services as they’re provisioned, the clean fix is a teardown + redeploy with the safe password, not an in-place change.

6. enable_prometheus is declared twice — Grafana runs even when you think it’s off

config-template.yml implies Prometheus/Grafana is off by default (# enable_prometheus: false). But group_vars/all.yml defines the key twicefalse, then true further down — and last-wins in YAML, so the effective default is true. That’s why the Grafana tasks ran (and hit gotcha #5) even though I never enabled them. If you don’t want the monitoring stack, set enable_prometheus: false explicitly in your config.yml so it overrides the duplicate.

7. tee-ing an ansible-navigator run hides the real exit code

The EE launches with --tty, so piping the run through tee sends output to the container’s PTY (the pipe stays empty) and reports the pipeline’s exit code (tee’s 0) rather than ansible’s. A run that actually failed looked like it succeeded. Watch the run with docker logs -f <ansible_runner_container> instead, and trust the PLAY RECAP failed= counts, not the shell exit code.


The deploy, stage by stage

One ansible-navigator run chains four playbooks. On my run — Apple-Silicon Mac, amd64 EE under emulation, default instance sizes — the full stand-up took about 2.5 hours end to end. The long poles are parcel distribution and bringing 14 Kerberized services up, not the Terraform infra (which was ~10 min); native amd64 and larger nodes would cut this down.

Stage Playbook What happens
1 infrastructure.yml Terraform: VPC, security groups, 11 EC2 nodes, generated SSH key
2 services.yml FreeIPA (DNS + Kerberos), PostgreSQL, Caddy/TLS, Node Exporter, Prometheus/Grafana
3 cms.yml Cloudera Manager install + license, CM agents, AutoTLS, CM Kerberos
4 ozone-cluster.yml CM builds the cluster: distribute/activate parcels, assign roles, start services

Every stage ended failed=0. The final recap across all 11 hosts:

PLAY RECAP
steven-ce-base-master-01.cldr.internal : ok=197 changed=75  unreachable=0 failed=0
steven-ce-base-master-02.cldr.internal : ok=197 changed=75  unreachable=0 failed=0
steven-ce-base-master-03.cldr.internal : ok=197 changed=75  unreachable=0 failed=0
steven-ce-base-worker-01.cldr.internal : ok=197 changed=75  unreachable=0 failed=0
   ... workers 02–04 identical ...
steven-ce-gateway-01.cldr.internal     : ok=93  changed=55  unreachable=0 failed=0
steven-ce-manager-01.cldr.internal     : ok=182 changed=79  unreachable=0 failed=0
steven-ce-sdx-01.cldr.internal         : ok=196 changed=75  unreachable=0 failed=0
steven-ce-services-01.cldr.internal    : ok=205 changed=121 unreachable=0 failed=0

One thing worth knowing: right after the Ozone stage completes, the CM cluster can briefly show BAD_HEALTH while ZooKeeper’s startup canary settles — it flips to GOOD on its own within a couple minutes. Don’t panic-restart it.

Cloudera Manager → All Hosts — every node in the cluster reporting Good Health and Commissioned


What you get at the end

A GOOD_HEALTH ozone-base-cluster on Cloudera Runtime 7.3.2, reachable through the Caddy reverse proxy on the single public node (the gateway) via a nip.io hostname:

  • Cloudera Manager: https://cm.<gateway-public-ip>.nip.ioadmin / your common_password
  • Cluster health (straight from the CM API): cluster GOOD_HEALTH; all 14 services GOOD — HDFS, Ozone, Kafka, YARN, Hive, Hive-on-Tez, HBase, Ranger, Knox, Atlas, Solr, ZooKeeper, Tez, Core Settings.
  • Only the gateway node has a public IP; every other node is private and reached through the proxy — the ring-fenced design the README promises.

Cloudera Manager home — ozone-base-cluster healthy on Cloudera Runtime 7.3.2 (Parcels), 8 hosts, every service green, with live cluster CPU / disk / network / HDFS charts


Cost control — pause, resume, tear down

The cluster bills ~$2/hr while it runs, so know the exits up front. All three are the same one-command shape:

# Pause — stop the EC2 instances, keep the EBS volumes + cluster state (cheapest way to keep it around)
ansible-navigator run playbooks/pause.yml -e @config.yml -m stdout

# Resume — start the instances back up
ansible-navigator run playbooks/resume.yml -e @config.yml -m stdout

# Tear down — Terraform destroys everything: instances, volumes, VPC
ansible-navigator run playbooks/infrastructure-teardown.yml -e @config.yml -m stdout

Teardown is a terraform destroy under the hood and finishes in a few minutes with a clean recap:

PLAY RECAP
localhost : ok=3 changed=1 unreachable=0 failed=0

Then confirm nothing is left billing before you walk away — Terraform state should be empty and AWS should report zero instances:

aws ec2 describe-instances --profile <your-profile> --region us-east-2 \
  --filters "Name=tag:deployment,Values=<name_prefix>" \
            "Name=instance-state-name,Values=running,pending,stopping,stopped" \
  --query 'length(Reservations[].Instances[])' --output text
# -> 0

What NOT to do

  • Don’t trust the :latest EE tag — pin 1.0.0-amd64.
  • Don’t assume console login = CLI creds — configure an SSO profile with account and role.
  • Don’t commit config.yml — it holds a plaintext password; keep it gitignored.
  • Don’t use the license .zipCDP_LICENSE_FILE wants the .txt.
  • Don’t leave it running unwatchedpause.yml or infrastructure-teardown.yml when you’re done.

Cloudera Community Edition on AWS in One Command

If you would like a deeper dive, hands on experience, demos, or are interested in speaking with me further about Cloudera Community Edition on AWS in One Command please reach out to schedule a discussion.