About Me

My photo
Rohit leads the Pivotal Labs App Modernization Practice in engineering, delivery training & cross-functional enablement, tooling, scoping, selling, recruiting, marketing, blog posts, webinars and conference sessions. Rohit has led multiple enterprise engagements including ones featured in the Wall Street Journal. Rohit focuses on designing, implementing and consulting with enterprise software solutions for Fortune 500 companies on application migration and modernization.

Tuesday, October 3, 2017

Pushing Zero Factor Apps to Cloud Foundry

Often times I hear the refrain that Pivotal Cloud Foundry is only suitable for cloud native apps that adhere to the 12 or 15 factors. This is like saying that the Spring is XML centric in 2017 or that Java EE is heavyweight in 2017. Cloud Foundry is a big tent platform that is suitable for running all kinds of workloads across languages and runtimes. 

In this post, lets disprove the myth by looking into what it will take to push a 0 factor app or a cloud-ANGRY app to Pivotal Cloud Foundry.  On a slight tangent the definition of cloud native keeps expanding. First we had the 12 factors from Heroku. Thereafter we added authentication/authorization, monitoring and API First making it 15. Recently Adrian Cockroft  espoused the following Cloud Native Principles 1. Pay As You go 2. Self Service- no waiting 3. Globally distributed by default 4. Cross-zone/region availability models 5. High Utilization- turn idle resources off and 6. Immutable Code Deployments.

So here it goes ....

1. One Codebase, one application
Violation of this rule leads to multiple application emerging from a single codebase or a mono-repo. Why is this pattern so bad - Because it leads to anti-patterns where updating one dependency leads to a cascade affecting all other apps built from the same repo. This  is akin to the domino effect. If you can sustain the pain of building all your apps from one repo - leading to a cascade of inter-dependent updates you don't need multiple repos. You can violate Conway's law - if you have a  complete holocracy. In fact google stores billions of Lines Of Code in a single monorepo.  Cloud foundry does NOT care if your app was built from one repo or sourced from multiple repos. 

2. API First
So this principle states that Built into every decision you make and every line of code you write is the notion that every functional requirement of your application will be met through the consumption of an API. You could of course  write code without interfaces or APIs or achieving market fit. At the extreme end you would ONLY build the API if you needed it and when there were multiple consumers and customers necessitating it. If you have only one consumer then why have a consumer driven contract. Talk the other team and establish a partner relationship. Again Cloud Foundry does NOT care if you are API first or API last. As app owners you have to live with the implications of this decision not the platform. 


3. Dependency Management
Software engineering practice dictates that we should explicitly declare and isolate dependencies. Most modern apps put an onus on independences i.e. package all the downstream dependencies including the application server in a fat jar. You don't rely on the implicit existence of system wide packages or mommy servers  like WebSphere. On the flip side you can definitely let the application server bring majority of the dependencies or use a method of layering with multi-buildpacks to inject your dependencies. Cloud Foundry has multiple options for pushing apps including ear, war, jar and docker files. Buildpacks like the Liberty Profile, JBOSS or TomEE will gladly allow you to keep your app lean and source all the dependencies from the application server classpath. So instead of a fat jar you can have the buildpacks create the Mommy server.

4. Design, Build, Release and Run
Strictly separate the build and run stages. Build one artifact and configure it at runtime for the environment. Release = (Build + Environment specific settings + app-specific configuration).  This principle allows for auditability and rollback. In the absence of separation in the build and run stages you are implicitly acknowledging that rollback is never an option and you don't need to track your past releases. Code and features are always moving forward in production. Everyone lives and codes on the master branch. You could completely violate this rule by packaging separate artifacts for every environment. As long as you have an ITIL process to validate such a process and the appropriate org structure it is possible to build apps-per env and throw them over the wall. Again this way severely defeats the purpose of devops and makes life of service reliability engineers ridiculously difficult. As long as you are using the environment variables from the right org and space you could pull this off with zero changes to the app in Cloud Foundry.

5. Configuration, Credentials and Code
Ideally you should keep this holy trinity separate from each other and bring them together ONLY at runtime in the platform; however I contend in 80% of the cases these three stay together and can be pushed together to Cloud Foundry. Of course the app is less secure and brittle since configuration is hardwired and credentials hardcoded, the app can still function correctly on Cloud Foundry. There is no explicit requirement that code should be separated from credentials and configuration should be externalized. As long as the properties are correct and location independent, the app will work with configuration bundled within the app 

6. Processes: Any state that is long lasting outside the scope of the request must be externalized. This allows for processes to be treated like cattle and not pets. This constraint can be violated by keeping long running state like caches, sessions and other user data across requests. As long as you can live with the risk of the occasional user seeing errant 500 responses when the server goes down this drawback can be tolerated. A side-effect of keeping state within the container is that your JVM sizes will be atypically large and rebalancing your app across diego cells will become time consuming.

7. Port Binding
The platform will perform the port management and assign the container a port through the environment instead of hardcoding the port in the server configuration. It is difficult to directly violate this principle since cloud foundry is responsible for port assignment and creation of routes to the app instance. Using container-to-container networking you may be able to overcome this constraint. 

8. Concurrency
Cloud apps should scale out using the process model. There is nothing in Cloud Foundry stopping you from scaling vertically instead of horizontally. Go crazy and push with a -Xmx of 16GB if your Diego cells  have the capacity.

9. Disposability
Ideally your app should not take more than 30s to startup or shutdown. One should always maximize robustness with fast startup and graceful shutdown. however if your app does a ton of startup initialization like priming caches or loading reference data then tune the cloud foundry cli to increase the push and the app start timeouts. If your application is BIG then you can cf push with a --no-start and then start it separately. When your app takes a significant time to startup and shutdown then it messes with the auto scaleup/scale down model of CF as well as uptime guarantees when cloud foundry redistributes your apps when new diego cells get on-boarded.

10. Dev/prod parity 
Try to keep development, staging, and production as similar as possible. This is like the advice to exercise an hour every day. Don't worry if your environments are lop-sided as long as there is some semblance of proportionality and the same PCF tech stack runs in every environment. 

11. Logs
You should treat logs as event streams unless you don't care about observability or if the logs are full of exceptions and totally useless. If the logging at INFO is so verbose so as to be of no use during debugging then don't worry about streaming the logs just call it in by persisting the logs to ephemeral disk or cheat by mounting a NFS volume and writing the logs to the volume services mounted NFS mount.

12. Admin Processes
If you don't run Run admin/management tasks as one-off processes then you will need to embed conditional logic  and APIs so as to externally trigger the admin task. You will need to map the same app under a different route to trigger a singleton instance of the management task.

13. Telemetry
Instrumenting the app for telemetry is only useful if you are going to watch the metrics and logs and take meaningful actions. In the absence of app telemetry rely on PCF metrics to provide app insight or in-built support with spring boot actuators which may be good enough for you. 

14. Authentication and Authorization
Do not rely on death star security to secure your apps. Ignore this principle at your own peril. Rely on the principle of least trust and industry standard protocols like OAUTH2 and OIDC or risk becoming the next Equifax

15. Backing Services
Treat backing services as attached resources. If you do violate this constraint then you need stateful constructs like NFS mount points via volume services and you will inject the bound service configuration in a CI pipeline. In a legacy app a lot of the backing service dependencies are already captured in a resources.xml surfaced via JNDI. Rely on buildpack auto-configuration magic to rewire these resources to cloud services automatically during cf push time.

Understand the tradeoffs and constraints and push zero factor apps to the cloud. Happy Hunting.

No comments:

Post a Comment