Summoning builders…Compiling dreams…Polishing pixels…Aligning the stars to your roadmap…Brewing the backlog…Forging gold from ideas…Charting the constellations…Waking the render spirits…

    Insights/Modernization & Legacy/Moving a monolith to Google Cloud Run without downtime

    Moving a monolith to Google Cloud Run without downtime

    Marco Lopes
    April 21, 2021
    2 views
    Modernization & Legacy
    Google Cloud
    Cloud Run
    DevOps
    Monolith Migration
    Zero Downtime
    Moving a monolith to Google Cloud Run without downtime

    Moving a monolith to Google Cloud Run without downtime

    By Marco Lopes · April 21, 2021 · Cloud & DevOps

    Moving a monolith does not have to mean dismantling it. At Kordspace, we approach cloud migrations as a sequence of reversible changes, not an opportunity to rewrite every part of an application at once.

    As a remote collaborative studio working across software, AI, Web3, hardware, and design, we care about infrastructure that makes delivery easier. Google Cloud is our preferred starting point, although we also work with AWS and Azure. For an HTTP application that fits its execution model, Google Cloud Run offers a useful middle ground: we keep our application together while handing container scheduling and scaling to a managed platform.

    The difficult part is not uploading a container. It is preserving behavior while two environments coexist. Here is how we plan that transition without a scheduled outage.

    Define what “without downtime” actually means

    We start by defining the user-visible contract. Existing sessions should survive. Requests should reach a healthy application. Writes should remain consistent. Scheduled work should not disappear or run twice merely because we changed hosting platforms.

    We also distinguish availability from perfection. An unexpected dependency failure can still interrupt service, and no deployment flag guarantees otherwise. Our goal is a migration that requires no planned interruption, with observable checkpoints and a credible rollback path.

    Before touching infrastructure, we map the application’s dependencies: database, cache, file storage, authentication, outbound services, callbacks, and background processes. We record which components hold state and which assume that only one application server exists.

    That inventory tells us whether Cloud Run is a suitable destination for the web process, and what must remain elsewhere.

    Make the monolith portable before moving it

    We first containerize the existing application and run it locally with production-like configuration. We avoid combining this work with a framework upgrade, database migration, or architectural redesign. Smaller changes are easier to explain when something breaks.

    For Cloud Run, the ingress container must listen on the port supplied through the PORT environment variable, on all interfaces rather than only localhost. We let the platform terminate TLS instead of managing public certificates inside the application container.

    We build one immutable image and promote that exact artifact through testing and production. A commit identifier makes releases traceable; an image digest makes the deployed contents unambiguous.

    Our container starts the web process directly, emits logs to standard output and standard error, and handles termination gracefully. We stop accepting new work and close resources promptly rather than assuming unlimited shutdown time.

    We also keep schema migrations out of container startup. Autoscaling can start several instances together; that is a poor place to coordinate database changes.

    Remove local state from the request path

    A monolith often works because its server quietly remembers things. Sessions live on disk, uploads land in a local directory, and generated reports wait for a later download.

    Those assumptions do not survive instance replacement or horizontal scaling. Cloud Run’s writable filesystem is ephemeral, and files written there consume instance memory. We reserve it for bounded temporary work, not durable application data.

    We move uploads and generated assets into Cloud Storage, reviewing permissions, object naming, and download behavior along the way. If old files remain on the original server, we copy them first and reconcile changes before switching the application’s storage configuration.

    For sessions, we use a shared store or appropriately secured signed cookies. Both environments must agree on cookie names, signing keys, domain settings, and expiry behavior.

    This compatibility matters during rollback too. A session created on Cloud Run should not become unreadable when a later request reaches the old deployment.

    Separate web requests from background work

    We inspect every process that runs alongside the web server. Cron tasks, queue consumers, email delivery, and image processing often hide inside a supposedly simple deployment.

    As of April 2021, we do not treat a Cloud Run service as an always-running worker. CPU availability is tied to request processing, so a thread launched after sending an HTTP response is not a reliable execution strategy.

    For bounded asynchronous work, we can use Cloud Tasks to call an authenticated HTTP handler. Pub/Sub push delivery can also fit event-driven processing. Scheduled HTTP work can begin with Cloud Scheduler. Long-running consumers or work that does not fit this model stay on suitable separate infrastructure.

    We make handlers idempotent because retries can happen. We also nominate exactly one owner for scheduled triggers during the transition. Running both environments is useful; accidentally running both sets of billing or notification schedules is not.

    Keep the database steady for the first cutover

    Changing compute and database hosting simultaneously creates two overlapping failure domains. Whenever possible, we move the web application first while retaining the existing database.

    That means proving connectivity early. We check network routes, authentication, encryption, latency, connection limits, and firewall rules from Cloud Run, not merely from a developer laptop.

    For Cloud SQL, we can use Cloud Run’s supported integration with a service identity granted the necessary access. Private connectivity may require a Serverless VPC Access connector and appropriate VPC configuration. We choose the route deliberately rather than assuming an internal address will be reachable.

    Autoscaling deserves particular attention. Each instance can create its own connection pool, so we budget connections across the maximum expected instance count, including the old environment during overlap. We tune pool sizes and set a sensible maximum instance limit, while keeping database-side safeguards.

    If relocating the database is unavoidable, we plan replication and cutover separately. A maintenance-free web deployment cannot compensate for an unavailable database.

    Use backward-compatible schema changes

    During a gradual migration, old and new application versions may serve requests against the same database. Both must understand the schema and data they encounter.

    We use an expand-and-contract approach. First, we add compatible structures, such as a nullable column or a new table. Next, we deploy code that tolerates both representations. We backfill data in controlled batches, then switch reads and writes when we have verified the result.

    Destructive cleanup comes later, after the rollback window has closed. Dropping a column because the new revision no longer needs it can make the old deployment impossible to restore.

    We also examine locking behavior. An apparently harmless schema statement can block writes depending on the database engine, table size, and operation. We test the migration against representative data and choose online techniques where supported.

    Rollback is only real when the previous application can still operate on the current data.

    Build a repeatable deployment path

    We use a pipeline to test the application, build the image, and publish it to a registry such as Container Registry. Cloud Build is a natural fit on Google Cloud, but the important property is repeatability rather than the particular CI product.

    We give the application a dedicated service account with narrowly scoped permissions. Deployment permissions belong to the delivery pipeline; runtime access belongs to the application identity. We never bake service-account keys or production secrets into the image.

    For secrets, we can retrieve values from Secret Manager through its API using that identity. We decide how initialization, caching, and rotation behave, including what happens if retrieval fails.

    Once the Cloud Run service exists, a candidate revision can be deployed without receiving normal service traffic:

    gcloud run deploy monolith \
      --image "$IMAGE_DIGEST" \
      --region europe-west1 \
      --platform managed \
      --no-traffic \
      --tag candidate
    

    The tagged revision gives us a target for smoke tests. We configure authentication explicitly; possessing a test URL is not an access-control policy.

    Test behavior, not just a health endpoint

    A successful deployment proves that a container started. It does not prove that the application works.

    We test login, session continuity, uploads, downloads, database writes, transactional email, and important third-party callbacks. We check redirects and absolute URLs because proxy headers and HTTPS handling often differ from the original server.

    We also test concurrent requests. A single container may receive multiple requests at once, so global mutable state and non-thread-safe libraries deserve scrutiny. We choose concurrency based on measured application behavior, not simply the largest available setting.

    Cold starts are another practical concern. We reduce unnecessary startup work and consider minimum instances where predictable response times justify the cost. Minimum instances help with readiness but do not eliminate every cold start during scaling or replacement.

    Finally, we send representative load through the candidate and observe database pressure, memory use, and downstream failures, not only HTTP response time.

    Move traffic through a reversible boundary

    Cloud Run revision traffic splitting helps us roll out versions within a service. It does not, by itself, split traffic between an existing server and Cloud Run.

    For that first move, we use a routing layer capable of reaching both environments. An existing reverse proxy may provide the transition point. A supported external HTTPS load-balancing design, using a serverless network endpoint group for Cloud Run, may also fit. We verify the actual routing capabilities before promising a percentage-based cutover.

    Where the routing layer supports it, we begin with internal users or a small, controlled portion of traffic. We watch errors, latency, session behavior, and database load before increasing exposure.

    If DNS is our transition mechanism, we lower TTLs in advance and expect overlap. Cached records and existing connections mean DNS changes are not an instantaneous switch or an instant rollback.

    We keep the old environment healthy until that overlap has passed. Both environments must accept the same public hostname and share compatible state throughout the transition.

    Make rollback operational, then retire carefully

    Before increasing traffic, we agree on rollback signals: elevated server errors, broken authentication, failed writes, or unacceptable latency on critical journeys. We assign responsibility for making the decision and document the exact routing change needed.

    We label logs with release information and use Cloud Logging and Cloud Monitoring to compare behavior. Application-level checks matter as much as infrastructure metrics: a fast response containing the wrong result is still a failure.

    Our short cutover checklist is:

    • [ ] Sessions, files, secrets, and database access work in both environments.
    • [ ] Schema changes remain compatible with the previous release.
    • [ ] Background tasks have a clear owner and tolerate retries.
    • [ ] Candidate behavior and load tests have passed.
    • [ ] Traffic reversal has been rehearsed and assigned.
    • [ ] The old environment remains available for the agreed rollback window.

    After the migration settles, we remove obsolete routes, triggers, credentials, and infrastructure deliberately. We retain the deployment records and update the runbook so the next release is routine rather than another migration project.

    Cloud Run can simplify operating a monolith without forcing a microservices rewrite. The safe path is to separate concerns, share durable state, and keep every traffic change reversible. If we can help plan that path for an application, start a project, or explore our related services to see how Kordspace approaches software delivery, cloud infrastructure, and collaborative product development.

    Conversation (0)

    No comments yet. Start the conversation.

    Join the conversation

    Create a free member account to comment.