184 lines
7.6 KiB
Markdown
184 lines
7.6 KiB
Markdown
# phpPgAdmin on Kubernetes
|
|
|
|
Production deployment for the brunner.ninja phpPgAdmin service. The repository
|
|
uses a small private image derived from the shared PHP/Apache base, a single
|
|
plain Kubernetes manifest, Traefik ingress, health probes, zero-unavailable
|
|
rolling updates, and restricted Gitea Actions deployment credentials.
|
|
|
|
## Architecture
|
|
|
|
- The repository image adds phpPgAdmin to the same Apache/PHP layer used by the
|
|
homepage deployments.
|
|
- CI deploys the image by its immutable Git commit tag rather than `latest`.
|
|
- `default/phppgadmin-config` mounts the complete multi-server configuration at
|
|
`/etc/phppgadmin/config.inc.php`.
|
|
- A ClusterIP Service exposes TCP port 80 to Traefik.
|
|
- `https://phppgadmin.brunner.ninja` redirects to the image's native
|
|
`/phppgadmin/` path.
|
|
- One replica uses `maxUnavailable: 0`, `maxSurge: 1`, and five seconds of
|
|
minimum readiness. The old pod remains available until the replacement has
|
|
passed its HTTP probe continuously for five seconds.
|
|
- CI places the Git commit in a pod-template annotation. This forces a safe
|
|
rollout when a ConfigMap-only change would otherwise leave a `subPath` mount
|
|
unchanged in the running container.
|
|
- Startup, readiness, and liveness probes request `/phppgadmin/` over HTTP.
|
|
|
|
The deployment is stateless. phpPgAdmin does not store database passwords or
|
|
sessions on a PVC; users authenticate directly to the selected PostgreSQL
|
|
server. The ConfigMap lists server addresses but contains no credentials.
|
|
|
|
## Container image
|
|
|
|
The Dockerfile installs the verified phpPgAdmin 7.14.7 release directly on
|
|
`registry.brunner.ninja/feedc0de/php-apache`. The common image already provides
|
|
Apache, PHP, PostgreSQL/PDO PostgreSQL, and OPcache, so Kubernetes nodes share
|
|
those layers with the PHP homepage deployments. The phpPgAdmin layer contains
|
|
only the application and its Apache alias configuration. Its base tag is the
|
|
same immutable php-apache Git commit used by the homepage values files.
|
|
|
|
The release archive is checked against the SHA-256 published by the Arch User
|
|
Repository package before extraction. phpPgAdmin is exposed at
|
|
`/phppgadmin/`, and the existing ConfigMap is mounted at
|
|
`/etc/phppgadmin/config.inc.php`.
|
|
|
|
## Repository contents
|
|
|
|
- `phppgadmin.yaml`: ConfigMap, Deployment, Service, Ingress, and Traefik root
|
|
redirect Middleware.
|
|
- `install.sh`: server-side dry-run, apply, and rollout verification.
|
|
- `test.sh`: local script and Kubernetes schema validation.
|
|
- `ci-deployer.yaml`: restricted Gitea Actions ServiceAccount, Role, binding,
|
|
and revocable token.
|
|
- `create-ci-kubeconfig.sh`: one-time CI identity bootstrap.
|
|
- `Dockerfile` and `phppgadmin.conf`: small phpPgAdmin layer and Apache alias
|
|
on the shared php-apache image.
|
|
- `.gitea/workflows/deploy.yml`: image build and validation on every push and
|
|
pull request; image publishing and deployment after a successful `main` run.
|
|
|
|
phpPgAdmin is intentionally not deployed through the generic PHP homepage Helm
|
|
chart. It is stateless, has no CephFS web root, and needs its own ConfigMap and
|
|
Traefik redirect Middleware.
|
|
|
|
## Prerequisites
|
|
|
|
- Kubernetes and kubectl.
|
|
- The unified `registry.brunner.ninja/feedc0de/php-apache` image in Quay.
|
|
- The `quay-pull-secret` image pull secret in the `default` namespace.
|
|
- Traefik with the `traefik.io/v1alpha1` Middleware CRD and ingress class
|
|
`traefik`.
|
|
- The existing Traefik `websecure` entrypoint and default
|
|
`*.brunner.ninja` TLS certificate.
|
|
- Network connectivity from the pod to every PostgreSQL hostname listed in the
|
|
ConfigMap.
|
|
|
|
Another cluster operator can reuse the manifest by changing the namespace,
|
|
database server list, ingress hostname, ingress class, and redirect Middleware.
|
|
|
|
## Install manually
|
|
|
|
With an administrator kubeconfig active, run:
|
|
|
|
```sh
|
|
./install.sh
|
|
```
|
|
|
|
The script performs a server-side dry-run, applies all resources, and waits up
|
|
to ten minutes for the Deployment. Existing resource names are preserved, so
|
|
the Service keeps its ClusterIP and the change rolls out in place.
|
|
|
|
Verify both application paths:
|
|
|
|
```sh
|
|
curl -I https://phppgadmin.brunner.ninja/
|
|
curl -I https://phppgadmin.brunner.ninja/phppgadmin/
|
|
```
|
|
|
|
The first should redirect permanently and the second should return the
|
|
phpPgAdmin application.
|
|
|
|
## Configure PostgreSQL servers
|
|
|
|
Edit `config.inc.php` inside the ConfigMap in `phppgadmin.yaml`. Each server
|
|
entry contains a display name, service DNS name, port, SSL mode, and default
|
|
database. Do not put PostgreSQL usernames or passwords into the ConfigMap or
|
|
Git repository.
|
|
|
|
The file is mounted using `subPath`, so Kubernetes does not update it inside an
|
|
already-running pod. The CI workflow replaces the manifest's `manual` revision
|
|
annotation with the Git commit SHA before applying it, which safely rolls the
|
|
pod for every merged configuration change. A manual `./install.sh` changes the
|
|
annotation back to `manual` and likewise triggers a rollout after a CI deploy.
|
|
|
|
## Validate locally
|
|
|
|
```sh
|
|
./test.sh
|
|
```
|
|
|
|
The test checks both shell scripts and validates the Kubernetes resources with
|
|
kubeconform. The Traefik Middleware is reported as skipped because kubeconform's
|
|
standard schema catalog does not contain cluster-specific CRDs; the Kubernetes
|
|
API validates it during the server-side dry-run.
|
|
|
|
## Automatic deployment with Gitea Actions
|
|
|
|
The workflow validates every push and pull request. A successful push to
|
|
`main` applies the complete `phppgadmin.yaml`, including ConfigMap, Deployment,
|
|
Service, Ingress, and Middleware, then polls the Deployment for up to ten
|
|
minutes.
|
|
|
|
The workflow builds and smoke-tests the image for every push and pull request.
|
|
On `main` it pushes the immutable Git commit tag plus `latest`, substitutes the
|
|
immutable tag in the deployment manifest, and deploys it. It requires these
|
|
Gitea Actions secrets:
|
|
|
|
- `QUAY_USERNAME`: Quay robot-account username with read/write access.
|
|
- `QUAY_TOKEN`: token for that robot account.
|
|
- `KUBE_CONFIG_BASE64`: the restricted phpPgAdmin deployment kubeconfig,
|
|
base64-encoded on one line.
|
|
|
|
### One-time CI setup
|
|
|
|
1. Run `./install.sh` with an administrator identity so every application
|
|
resource already exists.
|
|
2. Run:
|
|
|
|
```sh
|
|
./create-ci-kubeconfig.sh
|
|
```
|
|
|
|
3. Copy only the final base64 line into the Gitea repository Actions secret
|
|
named `KUBE_CONFIG_BASE64`.
|
|
4. Protect `main`, require successful validation before merging, and review
|
|
manifest and workflow changes.
|
|
5. Push a harmless README change and confirm the deployment job succeeds.
|
|
|
|
The workflow matches the existing brunner.ninja runner: Kubernetes API URL
|
|
`https://host.containers.internal:6443`, with TLS server name `192.168.0.2`.
|
|
Other clusters must change those values.
|
|
|
|
CI can get, patch, and update only these existing named objects:
|
|
|
|
- ConfigMap `phppgadmin-config`;
|
|
- Deployment and Service `phppgadmin`;
|
|
- Ingress `phppgadmin`;
|
|
- Middleware `phppgadmin-root-redirect`.
|
|
|
|
It cannot create or delete resources and cannot call the Kubernetes Secrets
|
|
API. Revoke it by deleting `default/phppgadmin-deployer-token` and the Gitea
|
|
secret. Rerunning the wrapper creates a replacement token.
|
|
|
|
Kubernetes RBAC cannot constrain individual fields inside an allowed
|
|
Deployment patch. A malicious change merged into `main` could alter the pod
|
|
specification. Branch protection and review therefore remain part of the
|
|
security boundary.
|
|
|
|
## Internet exposure
|
|
|
|
This instance is reachable from the public internet and presents direct
|
|
PostgreSQL username/password authentication. Use long, unique database
|
|
passwords, restrictive PostgreSQL roles and `pg_hba.conf` rules, TLS for remote
|
|
database connections, and external rate limiting or access control where
|
|
appropriate. phpPgAdmin's `extra_login_security` remains enabled in the
|
|
committed configuration.
|