Search code examples
dockerkubernetesdocker-composekompose

Running web application components as a single service versus multiple services in Kubernetes


I have a web applicaton (e.g. "india") that depends on postgres and redis (e.g. a typical Rails application).

I have a docker-compose.yml file that composes the containers to start this application.

version: '3'
services:

  redis-india:
    image: redis:5.0.6-alpine
    # .....

  postgres-india:
    image: postgres:11.5-alpine
    # ....

  india:
    depends_on:
      - postgres-india
      - redis-india
    image: india/india:local
    # ....

I'd like to run this application deployment with Kubernetes. I'm trying to figure out how to build the k8s resource objects correctly, and I'm weighing two options:

  1. I can build india, postgres-india, and redis-india as separate deployments (and therefore separate Services) in k8s

  2. I can build india, postgres-india, and redis-india as a single deployment (and therfore a single pod / service)

#2 makes more sense to me personally - all 3 items here comprise the entire "application service" that should be exposed as a single service URL (i.e. the frontend for the web application).

However, if I use an automated tool like kompose to translate my docker-compose.yml file into k8s resources, it follows approach #1 and creates three individual k8s Services.

Is there a "right way" or standard I should follow?

Thanks!


Solution

  • Independent components

    Your three components should run as separate deployments on Kubernetes. You want these three components to be:

    • Independently upgradable and deployable (e.g. you deploy a new version of Redis but not your app or database)
    • Independently scalable - e.g. you might get many users and want to scale up to multiple instances (e.g. 5 replicas) of your app.

    State

    Your app should be designed to be stateless, and can be deployed as a Deployment. But the Redis and PostgreSQL are stateful components and should be deployed as StatefulSet.

    Availability

    In a production environment, you typically want to:

    • Avoid downtime when upgrading any application or database
    • Avoid downtime if you or the cloud provider upgrade the node
    • Avoid downtime if the node crash, e.g. due to hardware failure or kernel crash.

    With a stateless app deployed as a Deployment, this is trivial to solve - run at least two instances (replicas) of it - and make sure they are deployed on different nodes in the cluster. You can do this using Topology Spread Constraints.

    With a stateful component as e.g. Redis or PostgreSQL, this is more difficult. You typically need to run it as a cluster. See e.g. Redis Cluster. But it is more difficult for PostgreSQL, you could consider a PostgreSQL-compatible db that has a distributed design, e.g. CockroachDB that is designed to be run on Kubernetes or perhaps consider CrunchyData PostgreSQL Operator.

    Pod with multiple containers

    When you deploy a Pod with multiple containers, one container is the "main" application and the other containers are supposed to be "helper" / "utility" containers to fix a problem for the "main container" - e.g. if your app logs to two different files - you could have helper containers to tail those files and output it to stdout, as is the recommended log output in Twelve-Factor App. You typically only use "multiple containers" for apps that are not designed to be run on Kubernetes, or if you want to extend with some functionality like e.g. a Service Mesh.