days
0
-28
0
hours
-1
0
minutes
-2
-2
seconds
-4
-5
search
Methodology and best practices

12 Factor App with MicroProfile and Kubernetes

Emily Jiang
microprofile
© Shutterstock / Paulo Jose Lima Gomes

The 12 Factor App approach is for defining a clean contract between the application and running environment. The application and running environment can focus on their own domain and will not step into each others’ toes.

You might have heard of the 12 Factor App application development methodology, published by Heroku, and specifically designed to create applications that will serve as the foundation for a Software-as-a-Service offering.

In this article, we will walk you through the 12 Factor App approach.

SEE ALSO: DevSecOps best practices for enterprises leveraging Kubernetes

Why the 12 Factor App?

The 12 Factor App approach for defining a clean contract between the application and running environment. The application and running environment can focus on their own domain and will not step into each others’ toes.

What is the 12 Factor App?

12 Factor App is a methodology, best practices, and manifesto that says apps should:

  • Use declarative formats for setup automation, to minimize time and cost for new developers joining the project;
  • Have a clean contract with the underlying operating system, offering maximum portability between execution environments;
  • Be suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration;
  • Minimize divergence between development and production, enabling continuous deployment for maximum agility;
  • And can scale up without significant changes to tooling, architecture, or development practices.

The 12 Factor App has 12 factors.

How to create a 12 Factor App

For the purposes of this article I will examine how to create a 12 Factor App using Eclipse MicroProfile and Kubernetes. MicroProfile is an open source project and community, established in 2016 by the members such as IBM, Red Hat, Tomitribe, Payara, LJC, and many others. In 2017, it was moved under Eclipse Foundation. It adopts lightweight, iterative processes and has a rapid release cycle.

It has 3 releases per year. In February 2010, MicroProfile released MicroProfile 3.3 including:

Eclipse MicroProfile has about a dozen implementations with various levels of support: Open Liberty, Quarkus, Payara, TomEE, Wildfly, KumuluzEE, Piranha, Apache Launcher, and others.

In this session, I will show you how to use some Eclipse MicroProfile Specs to fulfill the 12 Factor App. Let’s take a look at the 12 Factors one by one and focus on the how aspect.

1. Codebase

You can use GitHub repositories to provide a dedicated codebase per microservice so that each microservice can have its own release schedule. It is important that the codebase is version controlled.

Following the discipline of a single repository for an application forces the teams to analyze the seams of their application, and identify potential monoliths that should be split off into microservices.

2. Dependencies

Instead of packaging the third-party libraries inside your microservice, specify your dependencies in your Maven pom.xml or Gradle settings.gradle file. This enables you to freely move up to newer versions.

3. Config

Configuration is one of the most important factors. The golden rule of developing microservices is to write once, configure everywhere. When changing configuration, you don’t want to repackage your microservices. The way to achieve this is to externalise the configuration from the microservice application code. MicroProfile Config enables you to place configuration in properties files that can be easily updated without recompiling your microservices.

You can then use MicroProfile Config to access configuration “my.string.property” via

  • Programmatically lookup
    Config config =ConfigProvider.getConfig();
    config.getValue(“my.string.property”, String.class);
  • Via CDI Injection@Inject @ConfigProperty(name=”my.string.property”) String myPropV;

The property ”my.string.property” can be stored in System Properties, Environment Variables, or other config sources such as Kubernetes Config Map, etc. In this way, you can update the property without the need to repack the 12 Factor App.

4. Backing services

They should be configurable so that you can easily switch the attached resource by just changing the configuration. Again, MicroProfile Config and MicroProfile Rest Client can help here.
MicroProfile Rest Client enables the client to treat backing services as attached resources via the following mechanism:

  • Specify the interface of the backing services and annotate with @RegisterRestClient
    Package com.acme;
    @Dependent
    @RegisterRestClient
    @RegisterProvider(UnknownUrlExceptionMapper.class)
    @Path("/properties")
    public interface SystemClient {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Properties getProperties() throws UnknownUrlException, ProcessingException;
    }
  • Inject the backing service interface in the client
    @Inject @RestClient 
    private SystemClient defaultRestClient;
  • Bind the backing service via config property
    com.acme.SystemClient/mp-rest/url=http://localhost:9080/system

In this case, if you want to swap the attaching resources simply via updating the value of the binding property as long as the services implement the same interface.

5. Build, release, run

The build stage produces an image with the source code. If you use maven, your build stage is when you issue the maven build command, e.g. mvn package.

The Release stage takes the image from the build and package with the config. If you use Docker, the command ”docker build” maps to the release stage, which then pushes the docker image to Docker hub to get ready to move towards the run stage.

The run stage is to execute the image in the execution environment. You can take “docker run” or when you deploy images to Kubernetes when issuing “kubectl apply -f deployment.yaml”.

6. Processes

Microservices should be stateless. REST is a well-adopted transport protocol, and JAX-RS, included in MicroProfile, can be used to achieve a RESTful architecture. Systems that follow the REST paradigm are stateless. In this way, underlying infrastructure can destroy or create new microservices without losing any information.

When using JAX-RS, you will need to define a subclass of Application and JAX-RS resources.

@ApplicationPath("System")
public class SystemApplication extends Application {}

@Path("properties")
public class PropertiesResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getProperties() {…}
}

With the above 2 classes, the endpoint of http://host:port/System/properties will be exposed as a RESTful service.

7. Port binding

Export services using port binding. In the world of microservices, it is common that microservices talk to each other. When deploying them in the cloud, the ports need to change, so it is important to have a way to rebind the port.

MicroProfile Config can help this. You can specify the new port in Kubernetes ConfigMap, and MicroProfile Config automatically picks up the value to give the correct info to the deployed microservices. MicroProfile Rest Client can help with creating client code to connect from one microservice to another.

Refer to 4. Backing Services for the details of MicroProfile Rest Client. Once using MicroProfile, in a different environment, you can specify the backend binding service via the property of ${fully.qualified.interface.name}/mp-rest/url=http://localhost:9080/system

You can use MicroProfile config to specify the property value. If you specify the above property as an environment, you can swap any non-numeric-alpha with _ because of the restriction rules for naming environment variables. For instance, the property name com.acme.SystemClient/mp-rest/url will be
com_acme_SystemClient_mp_rest_url.

8. Concurrency

The microservices should be able to be scaled up or down, depending on the workload. Knative autoscaling can help with this.

9. Disposability

Microservices should start up and shut down very fast in order to save costs and improve efficiency. The microservices should be resilient.

MicroProfile Fault Tolerance can help out with this. MicroProfile Fault Tolerance provides the following annotations to make your microservices resilient.

@Retry - recover from a brief network glitch
@Timeout - specify a time out in order to prevent from waiting for a long period of time
@CircuitBreaker - prevent from a repeatable errors
@Bulkhead - prevent faults in one part of the system from cascading to the entire system
@Fallback - provide a backup plan

The following example demonstrates the Get operation onClientSide is very resilient. It will respond to its caller within 100 ms, no matter how slow the backend service is. If there is an exception, the fallback operation will be executed.

@Path("/client")
@ApplicationScoped
public class ClientController {
@Inject
@RestClient
private Service service;
@Inject @ConfigProperty(name="conference") String conf;
@GET
@Path("/test/{parameter}")
@Retry
@Timeout(100)
@Asynchronous
@Fallback(fallbackMethod = "fallback")
public CompletionStage<String> onClientSide(@PathParam("parameter") String parameter) {
return CompletableFuture.completedFuture("We are live @" + conf+ "! "+ service.doSomething(parameter));
}
public CompletionStage<String> fallback(@PathParam("parameter") String parameter) {
return CompletableFuture.completedFuture("This is my fallback!");
}
}

10. Dev/prod parity

The best practice of operating on microservices is keeping development, staging, and production environments as similar as possible in terms of code, people, and environment. Docker images are here to ensure that the environments stay the same so that you don’t run into the problem of “it runs on my laptop”.

SEE ALSO: Kubernetes: How to use readiness, liveness, and startup probes

11. Logs

Treat logs as event streams. The best practice is to system out your logs in JSON format and directly feed to ELK.

12. Admin processes

You should manage admin processes using Kubernetes Jobs so that the process will terminate automatically. You should not bake the admin processes into your microservices.

In summary, using MicroProfile and Kubernetes enables you to build a 12 Factor App. The code examples can be found from the following locations:

References

Author

Emily Jiang

Emily Jiang is a Java Champion. She is Liberty Microservices Architect and Advocate, Senior Techinal Staff Member (STSM) in IBM, based at Hursley Lab in the UK. Emily is an Eclipse MicroProfile guru and has been working on Eclipse MicroProfile since 2016 and leads the specifications of MicroProfile Config, Fault Tolerance and Service Mesh. She is also a CDI Expert Group member.

She is passionate about Eclipse MicroProfile and Jakarta EE. She regularly speaks at conferences, such as Code One, DevNexus, JAX London, Voxxed, Devoxx, EclipseCon, QCon, GeeCon, JFokus, etc. Follow her on Twitter @emilyfhjiang and/or connect with her on LinkedIn (https://www.linkedin.com/in/emily-jiang-60803812/)


Leave a Reply

avatar
400