Appendix: Background

Reference material for the FHIR Starter Project. The pages link here where it helps; you don’t need to read it from start to finish.

Kubernetes and Kustomize in Five Minutes

Your service runs in a Kubernetes cluster on your Mac, which OrbStack (or Docker Desktop) provides. You don’t need to know Kubernetes well for this guide, but these terms come up on almost every page.

Kubernetes

Term What it is in this guide

Namespace

A named group of objects in the cluster. Everything your local deployment creates is in the namespace fhir-starter-service-test, so add -n fhir-starter-service-test to your kubectl commands.

Pod

A running container: your service, WireMock, or cerner-mock. Kubernetes replaces a pod when its configuration changes, or when it crashes.

Deployment

Describes a pod: its image, its environment, and how many copies to run. Your deployments are fhir-starter-service-v1, wiremock, and from page 7 on, cerner-mock-v1.

Service

A stable name and port for a deployment’s pods. Inside the cluster, your service reaches WireMock at http://wiremock:8080, whichever pod is running.

ConfigMap

A set of files or settings that a deployment mounts into its pods or turns into environment variables. Your WireMock stubs, cerner-mock data, and overrides.env settings become ConfigMaps.

Readiness and liveness

Kubernetes checks whether each pod has started and is still running, and sends it requests only while it is ready. Your service reports that at /actuator/health/readiness, on port 8081. It is only ready when the health indicators of the services it calls are up: mvi, and from page 7 on, cerner. So a broken WireMock or cerner-mock keeps your service from becoming ready.

Port forwarding

Makes a port in the cluster available on your Mac. skaffold dev --port-forward=true forwards 8080 and 8081 to your service; kubectl port-forward forwards any other port, as you do for cerner-mock on page 7.

The kubectl commands you are most likely to need:

kubectl get pods -n fhir-starter-service-test                               # what is running, and its status
kubectl logs -n fhir-starter-service-test deploy/wiremock                   # the log of a deployment's pod
kubectl describe pod -n fhir-starter-service-test <pod name>                # why a pod doesn't start
kubectl port-forward -n fhir-starter-service-test svc/cerner-mock-v1 8090:8080

Kustomize

Kubernetes objects are described in YAML files. Kustomize builds the final YAML for an environment from shared pieces, so the files for your local cluster, staging, and production don’t repeat each other. Your kubernetes directory has three kinds of pieces:

  • base: the deployment and service of your own service, the same in every environment.

  • components: optional pieces that an environment adds. components/dev adds your local settings and test data; other teams' repositories provide components for WireMock and cerner-mock.

  • One directory per environment, such as dev-with-dependencies (your local cluster), sqa, or prod. Its kustomization.yaml lists the base and components it uses. Skaffold deploys dev-with-dependencies to your cluster.

A few things in the files you edit:

  • A component or resource can come from another repository, for example https://coderepo.mobilehealth.va.gov/scm/dhsss/wiremock.git//kubernetes?ref=v3.13.2. The ref is the branch or tag to use, so you get a fixed version.

  • The order of components matters: a component can only change objects that an earlier one created. That’s why WireMock comes before ../components/dev, which adds your stubs to it.

  • A configMapGenerator creates a ConfigMap from files (files:) or from a properties file (envs:). behavior: create makes a new ConfigMap; behavior: merge adds settings to one that another piece created. Kustomize adds a hash of the contents to the ConfigMap’s name, so a change to a stub gives the ConfigMap a new name, and Kubernetes replaces the pods that use it.

  • patches change objects that another piece created, for example to mount your stubs into the WireMock deployment. A patch finds its target by kind and metadata.name, and merges its fields into it.

Before Skaffold deploys, scripts/localize.sh copies everything, including the pieces from other repositories, into kubernetes/localized; see The Kubernetes Files Skaffold Deploys. To see the YAML that Kustomize builds for your local cluster, run:

kustomize build kubernetes/localized/dev-with-dependencies/dev-with-dependencies

What setup.sh Does

The script works through 10 steps. Each question shows its default in brackets:

Step What the script does What to answer

1. Bitbucket/DTR and Jira project

Sets the project the service belongs to. filegen uses it for the image paths

Enter (ckm and CKM)

2. Git repository

Notices that the service is inside your starter-project clone, and offers to add the service to that repository instead of creating a new one. Then it commits the generated files

Enter (y), then Enter for the commit message

3. filegen

Generates the kubernetes directory, skaffold.yaml, scripts, the Jenkins and V&V files, the DIBR README, and the Backstage files. This can take a minute

Enter for your user name and the Backstage system

4–6. skaffold.yaml, scripts, Vault

Adjusts skaffold.yaml for a single-module project, makes the Skaffold scripts executable, and turns off Vault for local deployments

Nothing to answer

7. Dynatrace labels

Sets the DynatraceDeploymentName and MAP-DHAS-ServiceName labels in kubernetes/base/deployment.yaml

Enter (MAP-DHAS-FSS-3443-fhir-starter-service-v1)

8. Product owner

Sets the product owner in metadata.yaml

Enter (leave it empty)

9. Build

Offers to build, deploy, and test the service

Enter (n); you’ll do this in the next section

10. Commit

Commits the files that the previous steps generated

Enter (y), then Enter for the commit message

Here is an abbreviated example of the output:

[2/10] Git repository
  The project directory is inside the git repository .../starter-project (origin: https://coderepo.mobilehealth.va.gov/scm/ckm/starter-project.git)
  Add fhir-starter-service to that repository instead of creating a repository for it? (y/n) [y]:
  Message of the initial commit [WIP: initial commit]:
  $ git add -A -- .
  $ git commit -q -m 'WIP: initial commit' -- .
  Done: Committed the generated project
  filegen reads the 'origin' remote only from a repository in the project directory, so it writes SET_ME
  instead of the Bitbucket project in catalog-info.yaml, mkdocs.yaml, and vv.yaml.
[3/10] Kubernetes, Skaffold, pipeline, and Backstage files (filegen)
  Backstage owner (your user name) [jdoe]:
  Backstage system (contract) [ckm]:
  filegen downloads its templates from coderepo. This can take a minute.
  $ ./mvnw -B -q gov.va.mobile.tools:ngss-filegen-maven-plugin:generate-kustomize-files ...
  Done: Generated the files
[4/10] skaffold.yaml for a single-module project
  Done: Removed 6 'project: fhir-starter-service' entries under 'jib:', and changed 1 'mvn -pl fhir-starter-service' to 'mvn -pl .'
...
Finished setting up fhir-starter-service
Replace the remaining placeholders:
  metadata.yaml:10:    # TODO: Document the settings of the service (see AppProperties and application.properties)
  ...
  vv.yaml:9:coderepo_project: "SET_ME" # coderepo project key

Setting Up a Service Without the Script

If you ever need to set up a generated service without the script, these are the equivalent manual steps:

  1. Commit the generated project. The build reads git information, so a commit is required before the first Maven build:

    git add .
    git commit -m "WIP: initial commit"
  2. Generate the Kubernetes, Skaffold, and other files with filegen. The full plugin name is required, because the filegen prefix can resolve to another plugin:

    F=gov.va.mobile.tools:ngss-filegen-maven-plugin
    ./mvnw $F:generate-kustomize-files $F:generate-skaffold-file $F:init-vv-yaml $F:generate-jenkinsfile \
        $F:generate-dibr-readme $F:init-backstage-files -Dbackstage.user=SET_ME -Dbackstage.contract=SET_ME
  3. filegen generates skaffold.yaml for a multi-module project. In skaffold.yaml, remove every project: fhir-starter-service entry under jib:, and in the post-sync-test profile, change mvn -pl fhir-starter-service …​ to mvn -pl . …​.

  4. Make the Skaffold scripts executable: chmod +x ./scripts/*

  5. Add SPRING_CLOUD_VAULT_ENABLED=false after SPRING_PROFILES_ACTIVE=dev in kubernetes/components/dev/overrides.env.

  6. In kubernetes/base/deployment.yaml, set the metadata:labels:DynatraceDeploymentName and spec:template:metadata:labels:MAP-DHAS-ServiceName labels to MAP-DHAS-FSS-3443-fhir-starter-service-v1.

  7. Commit the generated files:

    git add .
    git commit -m "Generated files added"

FHIR Server Properties

The Mobile Framework configures the FHIR server with va.mobile.fhir.server.* properties. These are the ones you are most likely to need; page 9 sets the server name and description:

Property Default Description

va.mobile.fhir.server.path

/fhir/*

Path of the FHIR server, relative to the context path

va.mobile.fhir.server.server-name

HAPI FHIR Server

software.name in the CapabilityStatement

va.mobile.fhir.server.implementation-description

HAPI FHIR

implementation.description in the CapabilityStatement

va.mobile.fhir.server.pretty-print

false

Indents JSON responses, which makes them easier to read

va.mobile.fhir.server.default-page-size and va.mobile.fhir.server.maximum-page-size

None

Page sizes of search results

va.mobile.fhir.server.logging.enabled

false

Logs every request with HAPI’s LoggingInterceptor