Skip to content

Playbook for migrating from Heroku to Control Plane, controlplane.com, and CPL CLI source

License

Notifications You must be signed in to change notification settings

dorongrinstein/heroku-to-control-plane

 
 

Repository files navigation

Heroku to Control Plane cpl CLI

A playbook for migrating from Heroku to Control Plane

RSpec Rubocop

Gem

This playbook shows how to move "Heroku apps" to "Control Plane workloads" via an open-source cpl CLI on top of Control Plane's cpln CLI.

Heroku provides a UX and CLI that enables easy publishing of Ruby on Rails and other apps. This ease of use comes via many "Heroku" abstractions and naming conventions.

Control Plane, on the other hand, gives you access to raw cloud computing power. However, you need to know precisely how to use it.

To simplify migration to and usage of Control Plane for Heroku users, this repository provides a concept mapping and a helper CLI based on templates to save lots of day-to-day typing (and human errors).

  1. Key Features
  2. Concept Mapping
  3. Installation
  4. Example CLI Flow for Application Build/Deployment
  5. Example Project Modifications for Control Plane
  6. Environment
  7. Database
  8. In-memory Databases
  9. Scheduled Jobs
  10. CLI Commands Reference
  11. Mapping of Heroku Commands to cpl and cpln
  12. Examples
  13. Migrating Postgres Database from Heroku Infrastructure
  14. Migrating Redis Database from Heroku Infrastructure
  15. Tips

Key Features

  • A cpl command to complement the default Control Plane cpln command with "Heroku style scripting." The Ruby source can serve as inspiration for your own scripts.
  • Easy to understand Heroku to Control Plane conventions in setup and naming.
  • Safe, production-ready equivalents of heroku run and heroku run:detached for Control Plane.
  • Automatic sequential release tagging for Docker images.
  • A project-aware CLI that enables working on multiple projects.

Concept Mapping

On Heroku, everything runs as an app, which means an entity that:

  • runs code from a Git repository.
  • runs several process types, as defined in the Procfile.
  • has dynos, which are Linux containers that run these process types.
  • has add-ons, including the database and other services.
  • has common environment variables.

On Control Plane, we can map a Heroku app to a GVC (Global Virtual Cloud). Such a cloud consists of workloads, which can be anything that can run as a container.

Heroku Control Plane
app GVC (Global Virtual Cloud)
dyno workload
add-on either a workload or an external resource
review app GVC (app) in staging organization
staging env GVC (app) in staging organization
production env GVC (app) in production organization

On Heroku, dyno types are specified in the Procfile and configured via the CLI/UI; add-ons are configured only via the CLI/UI.

On Control Plane, workloads are created either by templates (preferred way) or via the CLI/UI.

For the typical Rails app, this means:

Function Examples On Heroku On Control Plane
web traffic rails, sinatra web dyno workload with app image
background jobs sidekiq, resque worker dyno workload with app image
db postgres, mysql add-on external provider or can be set up for development/testing with Docker image (lacks persistence between restarts)
in-memory db redis, memcached add-on external provider or can be set up for development/testing with Docker image (lacks persistence between restarts)
others mailtrap add-on external provider or can be set up for development/testing with Docker image (lacks persistence between restarts)

Installation

  1. Install Node.js (required for Control Plane CLI).
  2. Install Ruby (required for these helpers).
  3. Install Control Plane CLI (adds cpln command) and configure credentials.
npm install -g @controlplane/cli
cpln login
  1. Install Heroku to Control Plane cpl CLI, either as a Ruby gem or a local clone. For information on the latter, see CONTRIBUTING.md.
gem install cpl

Note: Do not confuse the cpl CLI with the cpln CLI. The cpl CLI is the Heroku to Control Plane playbook CLI. The cpln CLI is the Control Plane CLI.

Example CLI Flow for Application Build/Deployment

Notes:

  • my-app is an app name defined in the .controlplane/controlplane.yml file, such as ror-tutorial in this controlplane.yml file.
  • Other files in the .controlplane/templates/ directory are used by the cpl setup-app and cpl apply-template commands.

Initial Setup and Deployment

For each Git project that you want to deploy to Control Plane, copy project-specific configs to a .controlplane/ directory at the top of your project. cpl will pick those up depending on which project folder tree it runs. Thus, this automates running several projects with different configs without explicitly switching configs.

Before the initial setup, add the templates for the app to .controlplane/controlplane.yml, using the setup key, e.g.:

my-app:
  setup:
    - gvc
    - postgres
    - redis
    - memcached
    - rails
    - sidekiq

Note how the templates correspond to files in the .controlplane/templates/ directory.

Then create a Dockerfile for your deployment. See this example.

# Provision infrastructure (one-time-only for new apps) using templates.
cpl setup-app -a my-app

# Build and push image with auto-tagging, e.g., "my-app:1_456".
cpl build-image -a my-app --commit 456

# Prepare database.
cpl run:detached -a my-app --image latest -- rails db:prepare

# Deploy latest image.
cpl deploy-image -a my-app

# Open app in browser.
cpl open -a my-app

Promoting Code Upgrades

# Build and push new image with sequential tagging, e.g., "my-app:2".
cpl build-image -a my-app

# Or build and push new image with sequential tagging and commit SHA, e.g., "my-app:2_ABC".
cpl build-image -a my-app --commit ABC

# Run database migrations (or other release tasks) with latest image, while app is still running on previous image.
# This is analogous to the release phase.
cpl run:detached -a my-app --image latest -- rails db:migrate

# Deploy latest image.
cpl deploy-image -a my-app

Example Project Modifications for Control Plane

See this for a complete example.

To learn how to migrate an app, we recommend following along with this example project.

  1. Create the .controlplane/ directory at the top of your project and copy files from the templates/ directory of this repository to something as follows:
app_main_folder/
  .controlplane/
    Dockerfile          # Your app's Dockerfile, with some Control Plane changes.
    controlplane.yml
    entrypoint.sh       # App-specific - edit as needed.
    templates/
      gvc.yml
      memcached.yml
      postgres.yml
      rails.yml
      redis.yml
      sidekiq.yml

The example .controlplane/ directory already contains these files.

  1. Edit your controlplane.yml file as needed. For example, see this controlplane.yml file.
# Keys beginning with "cpln_" correspond to your settings in Control Plane.

aliases:
  common: &common
    # Organization name for staging (customize to your needs).
    # Production apps will use a different organization, specified below, for security.
    cpln_org: my-org-staging

    # Example apps use only one location. Control Plane offers the ability to use multiple locations.
    default_location: aws-us-east-2

    # Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
    one_off_workload: rails

    # Workloads that are for the application itself and are using application Docker images.
    app_workloads:
      - rails
      - sidekiq

    # Additional "service type" workloads, using non-application Docker images.
    additional_workloads:
      - redis
      - postgres
      - memcached

    # Configure the workload name used when maintenance mode is on (defaults to "maintenance")
    maintenance_workload: maintenance

apps:
  my-app-staging:
    # Use the values from the common section above.
    <<: *common
  my-app-review:
    <<: *common
    # If `match_if_app_name_starts_with` is `true`, then use this config for app names starting with this name,
    # e.g., "my-app-review-pr123", "my-app-review-anything-goes", etc.
    match_if_app_name_starts_with: true
  my-app-production:
    <<: *common
    # Use a different organization for production.
    cpln_org: my-org-production
    # Allows running the command `cpl promote-app-from-upstream -a my-app-production`
    # to promote the staging app to production.
    upstream: my-app-staging
  my-app-other:
    <<: *common
    # You can specify a different `Dockerfile` relative to the `.controlplane/` directory (defaults to "Dockerfile").
    dockerfile: ../some_other/Dockerfile
  1. We recommend that you try out the commands listed in the example. These steps will guide you to the following:

    1. Provision the GVC and workloads.
    2. Build the Docker image.
    3. Run Rails migrations, like in the Heroku release phase.
    4. Promote the latest Docker image.

Environment

There are two main places where we can set up environment variables in Control Plane:

  • In workload/container/env - those are container-specific and must be set up individually for each container.

  • In gvc/env - this is a "common" place to keep env vars which we can share among different workloads. Those common variables are not visible by default, and we should explicitly enable them via the inheritEnv property.

Generally, gvc/env vars are useful for "app" types of workloads, e.g., rails, sidekiq, as they can easily share common configs (the same way as on a Heroku app). They are not needed for non-app workloads, e.g., redis, memcached.

It is ok to keep most of the environment variables for non-production environments in the app templates as, in general, they are not secret and can be committed to the repository.

It is also possible to set up a Secret store (of type Dictionary), which we can reference as, e.g., cpln:https://secret/MY_SECRET_STORE_NAME/MY_SECRET_VAR_NAME. In such a case, we must set up an app Identity and proper Policy to access the secret.

# In `templates/gvc.yml`:
spec:
  env:
    - name: MY_GLOBAL_VAR
      value: 'value'
    - name: MY_SECRET_GLOBAL_VAR
      value: 'cpln:https://secret/MY_SECRET_STORE_NAME/MY_SECRET_GLOBAL_VAR'

# In `templates/rails.yml`:
spec:
  containers:
    - name: rails
      env:
        - name: MY_LOCAL_VAR
          value: 'value'
        - name: MY_SECRET_LOCAL_VAR
          value: 'cpln:https://secret/MY_SECRET_STORE_NAME/MY_SECRET_LOCAL_VAR'
      inheritEnv: true # To enable global env inheritance.

Database

There are several options for a database setup on Control Plane:

  • Heroku Postgres. It is the least recommended but simplest. We only need to provision the Postgres add-on on Heroku and copy its XXXXXX_URL connection string. This is good for quick testing but unsuitable for the long term.

  • Control Plane container. We can set it up as a workload using one of the default Docker Hub images. However, such a setup lacks persistence between container restarts. We can use this only for an example or test app where the database doesn't keep any serious data and where such data is restorable.

  • Any other cloud provider for Postgres, e.g., Amazon's RDS can be a quick go-to. Here are instructions for setting up a free tier of RDS.

Tip: If you are using RDS for development/testing purposes, you might consider running such a database publicly accessible (Heroku actually does that for all of its Postgres databases unless they are within private spaces). Then we can connect to such a database from everywhere with only the correct username/password.

By default, we have structured our templates to accomplish this with only a single free tier or low tier AWS RDS instance that can serve all your development/testing needs for small/medium applications, e.g., as follows:

aws-rds-single-pg-instance
  mydb-staging
  mydb-review-111
  mydb-review-222
  mydb-review-333

Additionally, we provide a default postgres template in this repository optimized for Control Plane and suitable for development purposes.

In-memory Databases

E.g., Redis, Memcached.

For development purposes, it's useful to set those up as Control Plane workloads, as in most cases, they don't keep any valuable data and can be safely restarted, which doesn't affect application performance.

For production purposes or where restarts are not an option, you should use external cloud services.

We provide default redis and memcached templates in this repository optimized for Control Plane and suitable for development purposes.

Scheduled Jobs

Control Plane supports scheduled jobs via cron workloads.

Here's a partial example of a template for a cron workload, using the app image:

kind: workload
name: daily-task
spec:
  type: cron
  job:
    # Run daily job at 2am.
    schedule: 0  2  *  *  *
    # "Never" or "OnFailure"
    restartPolicy: Never
  containers:
    - name: daily-task
      args:
        - bundle
        - exec
        - rails
        - db:prepare
      image: "/org/APP_ORG/image/APP_IMAGE"

A complete example can be found at templates/daily-task.yml, optimized for Control Plane and suitable for development purposes.

You can create the cron workload by adding the template for it to the .controlplane/templates/ directory and running cpl apply-template my-template -a my-app, where my-template is the name of the template file (e.g., my-template.yml).

Then to view the logs of the cron workload, you can run cpl logs -a my-app -w my-template.

CLI Commands Reference

Click here to see the commands.

You can also run the following command:

cpl --help

Mapping of Heroku Commands to cpl and cpln

Heroku Command cpl or cpln
heroku ps cpl ps
heroku config ?
heroku maintenance cpl maintenance
heroku logs cpl logs
heroku pg ?
heroku pipelines:promote cpl promote-app-from-upstream
heroku psql ?
heroku redis ?
heroku releases ?

Examples

  • See the examples/ and templates/ directories of this repository.
  • See the .controlplane/ directory of this live example: react-webpack-rails-tutorial

About

Playbook for migrating from Heroku to Control Plane, controlplane.com, and CPL CLI source

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 99.7%
  • Other 0.3%