diff --git a/docs/appendices/file-formats/app-json.md b/docs/appendices/file-formats/app-json.md index b3cd72c7b..53924fabc 100644 --- a/docs/appendices/file-formats/app-json.md +++ b/docs/appendices/file-formats/app-json.md @@ -43,6 +43,7 @@ - `autoscaling` (map of string to object, optional) autoscaling rules. See the autoscaling section for more details - `max_parallel`: (int, optional) number of instances to deploy in parallel at a given time - `quantity`: (int, optional) number of processes to maintain. Default 1 for web processes, 0 for all others. +- `service`: (map of string to oject, optional) governs how non-web processes are exposed as services on the network ### Autoscaling @@ -82,6 +83,24 @@ An autoscaling trigger consists of the following properties: - `type`: (string, optional) - `metadata`: (object, optional) +### Service + +```json +{ + "formation": { + "internal-web": { + "service": { + "exposed": true + } + } + } +} +``` + +(object, optional) A key-value object specifying how to expose non-web processes as services. + +- `service`: (boolean, optional) Whether to expose a process as a network service. The `PORT` variable will be set to 5000. + ## Healthchecks ```json diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 2b23186ee..6a51e6510 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -232,6 +232,27 @@ The global default value may be set by passing an empty value for the option. dokku scheduler-k3s:set --global deploy-timeout ``` +### Exposing services on the network + +Dokku will automatically expose the `web` process as a Kubernetes Service, with all others being treated as background processes. In some cases, it may be useful to have other processes exposed as Kubernetes Service objects so as to segregate internal http endpoints from public http endpoints. This can be done by modifying the `app.json` Formation entry for your process type. + +```json +{ + "formation": { + "internal-web": { + "service": { + "exposed": true + } + } + } +} +``` + +In the above example, the `internal-web` process is exposed as a service. The `PORT` variable for the process will be set to `5000`, and a kubernetes `Service` object will be created pointing at your processes. + +> [!NOTE] +> It is not possible to modify the port mapping, nor is it possible to assign domains or SSL to a non-web process. + ### SSL Certificates #### Enabling letsencrypt integration diff --git a/plugins/app-json/appjson.go b/plugins/app-json/appjson.go index c071dc908..3133330c1 100644 --- a/plugins/app-json/appjson.go +++ b/plugins/app-json/appjson.go @@ -76,6 +76,16 @@ type Formation struct { // MaxParallel is the maximum number of processes to start in parallel MaxParallel *int `json:"max_parallel"` + + // Service is a struct that represents how to expose the process to the network + // This only applies to non-web processes + Service *FormationService `json:"service"` +} + +// FormationService is a struct that represents how to expose a process to the network +type FormationService struct { + // Exposed is whether or not the process is exposed as a service + Exposed bool `json:"exposed"` } // FormationAutoscaling is a struct that represents the autoscaling configuration for a process from an app.json file diff --git a/plugins/scheduler-k3s/templates/chart/deployment.yaml b/plugins/scheduler-k3s/templates/chart/deployment.yaml index 10455a329..f87e639b0 100644 --- a/plugins/scheduler-k3s/templates/chart/deployment.yaml +++ b/plugins/scheduler-k3s/templates/chart/deployment.yaml @@ -64,7 +64,11 @@ spec: {{- if hasKey $config "web" }} env: - name: PORT + {{- if eq $processName "web" }} value: "{{ $.Values.global.network.primary_port }}" + {{- else }} + value: "5000" + {{- end }} {{- end }} envFrom: - secretRef: diff --git a/plugins/scheduler-k3s/triggers.go b/plugins/scheduler-k3s/triggers.go index 1c12808ba..4ca9e52dc 100644 --- a/plugins/scheduler-k3s/triggers.go +++ b/plugins/scheduler-k3s/triggers.go @@ -623,6 +623,22 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e } sort.Sort(NameSorter(processValues.Web.PortMaps)) + } else if appJSON.Formation[processType].Service != nil && appJSON.Formation[processType].Service.Exposed { + processValues.Web = ProcessWeb{ + Domains: []ProcessDomains{}, + PortMaps: []ProcessPortMap{}, + TLS: ProcessTls{ + Enabled: false, + }, + } + + processValues.Web.PortMaps = append(processValues.Web.PortMaps, ProcessPortMap{ + ContainerPort: 5000, + HostPort: 5000, + Name: "http-5000-5000", + Protocol: PortmapProtocol_TCP, + Scheme: "http", + }) } values.Processes[processType] = processValues diff --git a/tests/unit/scheduler-k3s-4.bats b/tests/unit/scheduler-k3s-4.bats new file mode 100644 index 000000000..193ec1736 --- /dev/null +++ b/tests/unit/scheduler-k3s-4.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) app.json defined service" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_app_json + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get services $TEST_APP-web -o json | jq -r '.spec.ports[0].port'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "80" + + run /bin/bash -c "kubectl get services $TEST_APP-web -o json | jq -r '.spec.ports[0].targetPort'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5000" +} + +inject_app_json() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + cat <"$APP_REPO_DIR/app.json" +{ + "formation": { + "worker": { + "service": { + "exposed": true + } + } + } +} +EOF +}