The MLLP(Short for "Minimal Lower Layer Protocol") adapter is a component that runs on GKE, receives HL7v2 messages via MLLP/TCP, and forwards received messages to HL7v2 API.
- A Google Cloud project.
- A Docker repository. The following instructions assume the use of Google Container Registry.
- Installed gcloud and kubectl command line tools.
The prebuilt docker image is staged on Google Container Registry (GCR). It is strongly recommended to use the prebuilt docker images:
docker pull gcr.io/cloud-healthcare-containers/mllp-adapter:latest
Note that the 'latest' image tag is convenient for development and testing, but production deployments should use a stable image tag to ensure that a new image isn't unintentionally deployed whenever the mllp-adapter image is updated.
We use bazel as the build tool. Please refer to the bazel documentation to get started.
Run the following commands to build the MLLP adapter binary:
cd mllp_adapter
bazel build :mllp_adapter
Or to build and load a docker image that contains the binary:
bazel run :mllp_adapter_docker
If docker is installed on your system, you should be able to see the image:
docker images
To run the image locally:
docker run -p 127.0.0.1:2575:2575 -v ~/.config:/root/.config gcr.io/cloud-healthcare-containers/mllp-adapter /usr/mllp_adapter/mllp_adapter --hl7_v2_project_id=<PROJECT_ID> --hl7_v2_location_id=<LOCATION_ID> --hl7_v2_dataset_id=<DATASET_ID> --hl7_v2_store_id=<STORE_ID> --export_stats=false --receiver_ip=0.0.0.0 --pubsub_project_id=<PUBSUB_PROJECT_ID> --pubsub_subscription=<PUBSUB_SUBSCRIPTION_ID>
In the command above:
-p 127.0.0.1:2575:2575
is used to publish the port2575
of the MLLP container to host port2575
. By default MLLP adapter listen to port2575
;-v ~/.config:/root/.config
is used to give the container access to gcloud credentials;
Also note that:
PUBSUB_PROJECT_ID
andPUBSUB_SUBSCRIPTION_ID
are available by creating a pubsub topic and a subscription on Google Cloud;
You should be able to send HL7v2 messages now:
# This will fail because the format is invalid.
echo -n -e '\x0btestmessage\x1c\x0d' | telnet localhost 2575
NOTE: Older versions of the MLLP adapter subscribed to the single Pub/Sub topic configured in an HL7v2 store's
notification_config
field, and sent an outgoing message if a Pub/Sub notification had the "publish" attribute set (i.e. notification sent by the messages.create method). The new recommended method is to use the HL7v2 store's configurablenotification_configs
field to route messages to separate Pub/Sub topics and configure the MLLP adapter to subscribe to a topic that contains only messages it should send.To continue using the old behavior, set the
--legacy_publish_attribute=true
flag when running the image. This functionality is deprecated and the publish attribute will be removed in a future release.
If you want to use a custom service account instead of the default GCE service
account, make sure the custom service account is passed as --service-account
parameter while creating the cluster (See the
doc).
The custom service account will require the following permissions to work
properly: * roles/pubsub.subscriber. This is required only if you need the MLLP
adapter to listen for pubsub notifications and forward the HL7v2 messages to
HL7v2 stores. This role only needs to be set on a per-subscription basis. *
roles/healthcare.hl7V2Ingest. * roles/monitoring.metricWriter. This is for
writing metrics to Stackdriver. You don't need this if --exportStats
is set to
false
.
Before deploying the docker image to GKE you need to publish the image to a
registry. First modify BUILD.bazel
to replace my-project
and my-image
with
real values, then run:
bazel run :image_push
If this fails with the error message "ModuleNotFoundError: No module named 'urlparse'", you are hitting a python 3 bug in the google/containerregistry repo (google/containerregistry#42). Fix this by setting:
BAZEL_PYTHON=python2.7
If the push fails with "Permission denied", try running the following the command in the console and retry:
gcloud auth configure-docker
Next replace the placeholders in the deployment config for your use case.
Note that the <IMAGE_LABEL> should be a stable image tag that identifies the version of mllp-adapter image you want deployed. Do not use the 'latest' image tag, as this points to a new image each time there is an update.
You can retrieve the list of mllp-adapter image tags using the following command:
gcloud container images list-tags gcr.io/cloud-healthcare-containers/mllp-adapter
DIGEST TAGS TIMESTAMP
edea3487df8a 1969-12-31T19:00:00
231b073df13d blue,latest 1969-12-31T19:00:00
In the above example, the image tag 'blue' will be the stable image tag for the latest mllp-adapter image at the time of execution.
Deploy to a GKE cluster:
# To use default service account.
gcloud container clusters create mllp-adapter --zone=<ZONE_ID> --scopes https://www.googleapis.com/auth/pubsub
# Or to use custom service account. Read the documentation here: https://cloud.google.com/sdk/gcloud/reference/container/clusters/create
gcloud container clusters create mllp-adapter --zone=<ZONE_ID> --service-account <SERVICE_ACCOUNT>
# See the documentation here: https://kubernetes.io/docs/reference/kubectl/kubectl/
kubectl create -f config/mllp_adapter.yaml
kubectl create -f config/mllp_adapter_service.yaml
Google Binary Authorization Service can be applied as a deploy-time security control to ensure only trusted container images can be deployed. Please refer to Enable Binary Authorization with MLLP Adapter Deployment for details of setup.
Use E2E VPN setup if want your data to be encrypted end-to-end. See the "encryption in transit" doc for more details.
Cloud VPN creates a secure tunnel to ensure data is encrypted in transit.
First create a static IP address for the VPN gateway (GATEWAY_IP):
gcloud --project <PROJECT_ID> compute addresses create <IP_NAME> --region=<REGION_ID>
Then set up the VPN:
gcloud --project <PROJECT_ID> compute target-vpn-gateways create mllp-vpn --region <REGION_ID> --network <VPC_NAME>
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-esp" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "ESP" --target-vpn-gateway "mllp-vpn"
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-udp500" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "UDP" --ports "500" --target-vpn-gateway "mllp-vpn"
gcloud --project <PROJECT_ID> compute forwarding-rules create "mllp-vpn-rule-udp4500" --region <REGION_ID> --address <GATEWAY_IP> --ip-protocol "UDP" --ports "4500" --target-vpn-gateway "mllp-vpn"
Next set up the tunnels (also in the opposite direction which isn't shown here):
gcloud --project <PROJECT_ID> compute vpn-tunnels create "tunnel-1" --region <REGION_ID> --peer-address <PEER_IP_ADDRESS> --shared-secret <SHARED_SECRET> --ike-version "2" --target-vpn-gateway "mllp-vpn" --local-traffic-selector="10.100.0.0/24"
gcloud --project <PROJECT_ID> compute routes create "tunnel-1-route-1" --network <VPC_NAME> --next-hop-vpn-tunnel "tunnel-1" --next-hop-vpn-tunnel-region <REGION_ID> --destination-range "10.101.0.0/24"
Finally configure the firewall to allow traffic if necessary:
gcloud --project <PROJECT_ID> compute firewall-rules create allow-mllp-over-vpn --network <VPC_NAME> --allow tcp:32577,icmp --source-ranges "10.101.0.0/24"
The docker VPN image used in this section is here on github. First check the configurations in the repo to see if you need to make any changes for your use case (you probabaly do).
Pull the image from docker hub and upload it to gcr.io:
# Build the image from source code instead of pulling it if you made changes in previous step.
docker pull philplckthun/strongswan
docker tag philplckthun/strongswan:latest gcr.io/<GCR_PROJECT_ID>/<IMAGE_NAME>:<IMAGE_LABEL>
gcloud docker -- push gcr.io/<GCR_PROJECT_ID>/<IMAGE_NAME>:<IMAGE_LABEL>
Replace the placeholders in the deployment config.
Load necessary kernel modules before applying the configuration changes. Then apply the changes:
kubectl apply -f config/mllp_adapter_e2e.yaml
(Optional) Allocate a static IP address for the load balancer:
gcloud --project <PROJECT_ID> compute addresses create vpn-load-balancer --region=us-central1
Update the service config to expose it as an external load balancer.
Apply the change as well by running kubectl apply -f config/mllp_adapter_service_e2e.yaml
This will also update the firewall rules automatically.
Now connect your client to the VPN server and test if you can access the adapter:
kubectl describe pods | grep IP: # Get pod IP.
echo -n -e '\x0btestmessage\x1c\x0d' | telnet <POD_IP> 2575
To view the running status and logs of the pod:
kubectl get pods
kubectl logs <POD_ID>