Private Spaces Dyno DNS Service Discovery
Last updated 19 May 2017
Table of Contents
Dyno DNS Service Discovery is a beta feature. Please use with caution. If you have feedback, we’d love to hear from you at: firstname.lastname@example.org
Dyno DNS Service Discovery enables dynos in a private space to look up the IP address of other dynos using DNS. Dyno DNS names are composed from the app name, process type and dyno number.
Unlike in the Common Runtime, dynos in Private Spaces can freely exchange TCP and UDP traffic on a private network. A process type of any kind (web or worker) can choose to listen on any available port and other dynos in the same space can connect to that port on the dyno host.
To connect to another dyno, a client must know the port number and the IP address of the destination dyno. The port number is generally known in advance since server processes will listen on a fixed port number. The IP address cannot be known in advance. Instead, Heroku provides a DNS based naming scheme for dynos so client code is able to find and connect to any dyno on the network by knowing its app name, process name and optionally dyno number.
Enable Dyno DNS lookups
While this feature is in beta, you will need to explicitly enable it on each app that performs lookups. Use the following command:
$ heroku labs:enable spaces-dns-registry --app myapp
If the app is already running, you will need to restart dynos before this takes effect:
$ heroku restart --app myapp
Once enabled, the app’s dynos will have a slightly different DNS configuration that enables DNS lookups of dynos in the space. There is a slight risk that such DNS reconfiguration may cause issues for your application. See known caveats below. If you suspect any such problems, please report your issues in a support ticket and, if necessary, turn the feature off and restart dynos again:
$ heroku labs:disable spaces-dns-registry --app myapp $ heroku restart --app myapp
DNS Answers for a process type
The following example output shows how an nslookup might look like when performed from a one-off dyno inside a Space. In this case, there is an application named myapp with a web process type, scaled to 3 dynos.
$ nslookup web.myapp.app.localspace web.myapp.app.localspace. 0 IN A 10.10.10.11 web.myapp.app.localspace. 0 IN A 10.10.10.10 web.myapp.app.localspace. 0 IN A 10.10.10.9
DNS Answers for a specific process
The following example output shows how an nslookup might look like when performed from a one-off dyno inside a Space. In this case, there is an application named myapp with a web process type, scaled to 3 dynos, and we are going to look up web.2 (the second dyno of this process type)
$ nslookup 2.web.myapp.app.localspace 2.web.myapp.app.localspace. 0 IN A 10.10.10.10
Dyno DNS is a simple and useful feature that facilitates the creation and deployment of micro services. Suppose that you intended to deploy an application on Heroku that consisted of several small services - such as an image processing system.
Maybe this hypothetical system has a web process for accepting jobs, a worker process for processing the images, and an agent for handling back office interaction.
Using the DNS registry, services like these now have a simple way of understanding how they can reach one another to perform the task at hand. So, when the web process accepts a new job, it could alert the back office service via its DNS name that a new job has arrived and then send the job to the worker process via its DNS name as well.
DNS name format
Dyno DNS introduces a standardized DNS hostname format that allows users to be able to use convention to determine the address they can use to reach other formations in their Space. The root of the DNS name is
app.localspace - from there the application name and process type are added as the third and fourth level domains, respectively. For example, if you were to have a
worker Process type in an app named
awesomeapp, your Space would have a round robin DNS name configured for
worker.awesomeapp.app.localspace, which would answer for all the dynos in that process type. Every dyno has its own unique DNS name too, which is the number of the dyno as the 5th level domain - so
1.worker.awesomeapp.app.localspace for example.
This feature also introduces 4 new environment variables.
|HEROKU_DNS_FORMATION_NAME||The round robin DNS name of the dynos process type|
|HEROKU_DNS_DYNO_NAME||The DNS name of the dyno|
|HEROKU_DNS_APP_NAME||The dynos application DNS name|
|HEROKU_PRIVATE_IP||The private IP of the dyno|
HEROKU_DNS_APP_NAME does not resolve by itself - it is only provided to simplify determining the application level hostname for the dyno.
All dyno-to-dyno communication in Private Spaces takes places via the Private Network. To communicate via the Private Network, applications will need to bind to the Private IP address assigned to the dyno via a port of your choosing. Dynos cannot use privileged ports, so you must select a non-privileged port in the range 1024-65535. The correct Private IP for the application to bind to can be determined via the
HEROKU_PRIVATE_IP mentioned above. Once this has been done, the dyno will be reachable on the chosen port via its DNS name, which can either be directly reached via its unique dyno DNS name, or via the process types DNS name in round robin fashion.
The DNS registry is updated with up-to-date dyno locations every 10 seconds. This should be taken into account when designing service to run in the space - every dyno can see different updates at different times, but will converge after approximately 10s.
Dyno DNS records always have a time-to-live (TTL) of zero seconds, meaning that the requester should not itself cache the information. It is important that your application is properly configured to respect the TTLs in the DNS response, so it does not cache these local requests.
Once Dyno DNS is enabled, IPv6 will be disabled on the application’s dynos. Although Private Spaces only offers connectivity via IPv4, it is possible that your application’s behavior will change once IPv6 is not available on the dyno. For example, in Go you will need to Listen via
tcp4 in place of
tcp. For example, the following code:
conn, err := net.Dial("tcp", "golang.org:80") ln, err := net.Listen("tcp", ":8080")
will need to be replaced with
conn, err := net.Dial("tcp4", "golang.org:80") ln, err := net.Listen("tcp4", ":8080")