Skip to content

Conversation

@bleege
Copy link

@bleege bleege commented Sep 28, 2019

In order for SteVe to be deployed on Heroku (and likely other cloud hosting providers) the internal Jetty server needs to be able to be dynamically configured externally. Specifically, it needs to be able to have its port and serverHost configured at launch time.

This change is Heroku specific in that it checks for the PORT environment variable and if found sets Jetty to use that value and sets the serverHost to null so that SteVe can bind to it's hosting environment. If this environment variable is not found, SteVe runs as originally designed using the http.port and server.host from main.properties. For more details please see:

https://devcenter.heroku.com/articles/setting-the-http-port-for-java-applications

Relatedly, another way around this issue would be for SteVe to be refactored to support being run as an external .war file using Jetty Runner or Heroku Webapp Runner.

Thanks for considering this change!

/cc @goekay

@goekay
Copy link
Member

goekay commented Sep 29, 2019

hi brad, thank you for your effort! is this everything that is necessary to run steve on heroku? what about managed database instance or logging?

@goekay
Copy link
Member

goekay commented Sep 29, 2019

expanding on your commit, i think i have an idea how to improve the solution by using the way spring boot handles setting ports:

server:
    port: $PORT

port can be set to a number or to a system env, which is looked up if it starts with a dollar sign. we can apply the same approach when reading all properties. this is a little more flexible than hardcoding heroku-specific java logic.

goekay added a commit that referenced this pull request Sep 29, 2019
@bleege
Copy link
Author

bleege commented Sep 29, 2019

👋 @goekay ! Thanks for being open to my PR / feature request. I greatly appreciate it! 🎉

For simplicity and to continue to support SteVe's use by other people sakes' via SteVe's instructions my initial commit / PR was deliberately simple and focused only on the port and host name as that's fundamental. I'm totally happy to widen the scope of this PR if you would prefer that approach. I wanted to err on the side of simplicity since this was my first PR to this project. Let me explain further in my responses to your questions:

is this everything that is necessary to run steve on heroku?

No. Heroku's Java support comes in two flavors: WAR files on Tomcat and Plain Java. Plain Java is a very open / flexible option as it's possible to deploy apps with Embedded Tomcat / Jetty as well as plain JAR files. Complicating things further Heroku supports 2 ways to deploy Java applications: Via Git and Executable JAR files (see links below for more details).

Ideally, I'd prefer to be able to compile and deploy SteVe to Heroku via a Maven command using the Executable JAR method or via a simple WAR file. Currently I'm customizing SteVe properties along with the server and port fixes in SteveConfiguration.java, building, then manually committing the produced steve.jar and it's dependencies to a separate git repository for Heroku with a Procfile, and then pushing that git project to Heroku to deploy. This is a lot of tedious and error prone work which is genesis of my desire to automate this if possible. Again, thank you for being open to helping support this! 👍

Java Support
https://devcenter.heroku.com/articles/java-support

Deploying Spring Boot Applications To Heroku
https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku

Deploying With Git
https://devcenter.heroku.com/articles/git

Deploying Executable JAR Files
https://devcenter.heroku.com/articles/deploying-executable-jar-files

what about managed database instance

Heroku only supports Postgres, Redis, and Kafka directly themselves. They do have 3rd party support of MySQL via ClearDB and JawsDB. Due to SteVe's need to support Transactions only JawsDB allows this (which is what I'm using). Ideally SteVe would support a variety of databases, starting with Postgres due to it being open source and supported by Heroku, and it looks like it could quite easily via it's existing use of Hibernate ORM. This isn't an immediate blocker, but it's very much desired.

https://devcenter.heroku.com/categories/data-management

or logging?

Heroku's logging is a bit of a black box (to say the least IMHO). While it in theory supports external log aggregators (see link below), my basic goal is to get it to support System.out for SteVe so that it's visible via the app's management console in Heroku. I've been able to accomplish this by extending SteVe's log4j2.xml file. I purposely didn't include this in my initial PR as I didn't want to set this as the default for SteVe and SteVe's existing user base. I'll add this to my dev branch now though for the sake of transparency while we work through this PR. I totally expect it to be removed prior to merging however so that it doesn't expose logging to those not expecting it.

https://devcenter.heroku.com/articles/logging

expanding on your commit, i think i have an idea how to improve the solution by using the way spring boot handles setting ports:

I like this much better than my original solution as it' much lower level on the stack and avoids the platform specific spaghetti code. The only question I have about it is how to also null out the server host name in this scenario? Heroku needs to be able to set the port number AND have Jetty not bind to a particular host name. Unfortunately, Heroku only signifies this via setting the port number via environment variable and not also via host name. Thoughts?

Again, thank you for considering this PR and adding support for Heroku based deployments of SteVe!

@goekay
Copy link
Member

goekay commented Sep 29, 2019

  • on the topic of war vs executable jar files:
    building steve as a war file was our previous approach. but i changed it because of reasons explained in the last part of this comment. to be fair, i don't want to go back.

  • on the topic of databases:
    yeah, i kinda regret the decision to use mysql instead of postgres. but it is what it is and we have to live with it. i do not favor the approach of abstracting the database further away by using jpa/hibernate. this is the reason why we use jooq.

  • on the topic of logging:
    we started using various maven profiles and configuration files for different environments. i would advise to introduce another profile for heroku with its specific build and logging configuration. (and therefore i don't want to merge your last commit which modifies the prod profile, since there might be folks who use this profile already and expect logging to file only)

  • on the topic of hostname:
    are you sure that this has to be null? because even if you do not set it, jetty will try to figure out the hostname to bind to. you can set it to 0.0.0.0 which makes jetty listen to all interfaces as explained here. isn't this the wanted effect?

@bleege
Copy link
Author

bleege commented Sep 30, 2019

on the topic of war vs executable jar files:

I totally agree with you on this. Besides, at this point in time it's much easier / less work to make SteVe work with Heroku as is rather than to refactor everything to produce a WAR file. 👍

on the topic of databases:

No worries. Lack of Postgres is not a blocker for me at this point due to Heroku's integration with JawsDB. JOOQ has support for Posgres already so perhaps at a later date the SteVe's Maven pom.xml file can be updated to allow code generation support for either MySQL or Postgres depending on what the developer wants to use. No need to worry about this at this time.

on the topic of logging:

Yep, also very much agreed. Please DO NOT merge my commit 690fa03 for this very reason. 😄

on the topic of hostname:

This admittedly is a weird situation as Heroku doesn't provide any documentation on this (at least that I could find anyway). From my trial and error experiments the only way I could get SteVe to run on Heroku was with both the port being set via Heroku's environment variable AND the serverHost being set to null as showing my commit (see link below). My hunch is that Jetty treats this like 0.0.0.0 being set. The reason for this being necessary is that Heroku has a Web server (running on ports 80 and 443) that fronts the Java Web server running the application so the Java Web server needs to be able to bind to whatever port and host it finds itself deployed on.

https://github.com/RWTH-i5-IDSG/steve/blob/690fa03910b525ed5af61c5b21b04cbb3e899221/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java#L70-L81

With this all in mind, here's what I propose:

  1. Keep SteVe using an embedded Jetty Server
  2. Enable external setting of Jetty's port and serverName as my initial commit does. This commit is NOT required as the implemented solution / merged solution... it is just an example of what is actually needed from a technical point of view.
  1. Add a Heroku deployment command to SteVe's Maven pom.xml. This would compile, package, and deploy SteVe to Heroku by providing the following 3 files:
  • Procfile
    • web: java -jar steve.jar
  • steve.jar
    • Fat jar file including all dependencies
  • sytem.properties file
    • java.runtime.version=11

What do you think?

As for my personal use case, just having the external setting of Jetty's port and serverName is the priority. Having the Heroku Maven command is a "nice to have" and can built at a later date if desired.

Thanks again for your time and willingness to look into supporting deployment to Heroku. I really appreciate it! 👍

@goekay
Copy link
Member

goekay commented Sep 30, 2019

external setting of the port is already enabled with my latest commit. can you please test 0.0.0.0? in short, can you please test the following entries in main.properties and report back whether they work?

server.host = 0.0.0.0
http.port = $PORT

@bleege
Copy link
Author

bleege commented Oct 3, 2019

@goekay I was able to test out the server.host = 0.0.0.0 option tonight and believe it'll work as the Jetty docs say that it will.

Host
You can configure a host either as a host name or IP address to identify a specific network interface on which to listen. If not set, or set to the value of 0.0.0.0, the connector listens on all local interfaces. The XML Property element is used to look up the host value from the jetty.host property.

Let's go with your solution of:

server.host = 0.0.0.0
http.port = $PORT

Thanks again for your help and willingness to collaborate! I really appreciate it. 👍

@goekay
Copy link
Member

goekay commented Oct 3, 2019

that's great.

i believe that even server.host = localhost should work, because jetty starts with localhost if the property is null (i tested this) and some heroku documentations/tutorials start the server at localhost, as well.

@bleege
Copy link
Author

bleege commented Oct 3, 2019

Excellent! How should we go about finishing this PR? Do you want to finish your implementation and merge?

@goekay
Copy link
Member

goekay commented Oct 4, 2019

i am not sure that any additional changes for steve are necessary. in order to build and run steve on heroku, a better solution might be the heroku cli, which eliminates the necessity to modify pom.xml. the only required change is the addition of a Procfile and the contents of it would be so simple, that i would rather not pollute this repo with it.

edit: since i consider my only commit to provide the necesary changes for heroku and it is already in master branch, i think this problem is solved and propose to close this PR. no?

@bleege
Copy link
Author

bleege commented Oct 4, 2019

Sounds good to me. This PR should serve as a good document for others looking to run SteVe on Heroku. I'll close it out now and if others feel the need to expand upon it then they can.

Thanks again for everything!

@bleege bleege closed this Oct 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants