1 of 31

HOW TO MIGRATE

From Heroku to GCP / K8S

Node.js Paris, October 18th, 2018

Adrien JOLY, Software Engineer

Site Search Crawler

adrien.joly@algolia.com

2 of 31

1. What & why?

2. The K8S way

3. Deployment

4. Database

5. Env. vars

6. SSL

3 of 31

What

Algolia

Site Search crawler

Heroku & Docker compose

4 of 31

Algolia

Search Index

Front-end

5 of 31

Algolia

Database

Search Index

Front-end

6 of 31

Algolia & Site Search crawler

Database

Search Index

Front-end

Site Search Crawler

7 of 31

Crawler Architecture (1/3)

Search Index

Website

Workers

8 of 31

Crawler Architecture (2/3)

Search Index

Website

Database

Job Queue

Workers

Manager

Admin

API

9 of 31

Crawler Architecture (3/3)

Search Index

Website

Database

Job Queue

Workers

PDF Proxy

SPA Proxy

Manager

Admin

API

10 of 31

11 of 31

Deploy to Heroku

$ git push heroku

12 of 31

Local run 1/2

services:

db:

image: postgres:9.6.10

ports:

- "26432:5432"

redis:

image: redis:latest

ports:

- "36379:6379"

broker:

image: rabbitmq:3-management

ports:

- "26672:5672"

rendertron:

image: algolia/rendertron

ports:

- "23000:3000"

docker-compose.yml + shared .env file

13 of 31

Local run 2/2

$ docker-compose up -d

$ yarn dev

14 of 31

Why GCP/K8S

Compliance

Ownership

Cost

15 of 31

16 of 31

17 of 31

=

K8S

deploy and orchestrate�containerized applications

18 of 31

The K8S way

19 of 31

Differences

Deployment

Connecting to a database

Environment variables

HTTPS/SSL certificate

20 of 31

Deployment

Before K8S

01-redis.yml

02-rabbitmq.yml

03-cloudsql-proxy.yml

10-env-vars-common.yml

30-crawler-manager.yml

50-crawler-web.yml

70-crawler-worker.yml

99-crawler-ingress.yml

db-migrate.yml

Dockerfile

K8S: Dockerfile, YAML files and scripts

k8s-apply.sh*

k8s-auth.sh*

k8s-build.sh*

k8s-deploy.sh*

k8s-restart.sh*

k8s-run-job.sh*

  • Heroku: 1 procfile + add-ons + environment variables + git
  • docker-compose: 1 yaml file + 1 shared .env file + docker

21 of 31

Deploy to K8S

1/3

---

apiVersion: apps/v1beta1

kind: Deployment

metadata:

name: rabbitmq

spec:

replicas: 1

template:

metadata:

labels:

app: rabbitmq

spec:

restartPolicy: Always

terminationGracePeriodSeconds: 60

containers:

- name: rabbitmq

image: rabbitmq:3-management

imagePullPolicy: Always

ports:

- containerPort: 5672

- containerPort: 15672

E.g. 02-rabbitmq.yml

---

apiVersion: v1

kind: Service

metadata:

name: rabbitmq

# => exports env var RABBITMQ_SERVICE_HOST=10.7.250.108 (value may change)

spec:

type: NodePort

ports:

- port: 5672

name: "main"

# => exports env var RABBITMQ_SERVICE_PORT_MAIN=5672

- port: 15672

name: "ui"

# => exports env var RABBITMQ_SERVICE_PORT_UI=15672

selector:

app: rabbitmq

22 of 31

Deploy to K8S

2/3

---

kind: Deployment

replicas: 1

image: rabbitmq:3-management

---

kind: Service

# => exports RABBITMQ_SERVICE_HOST=10.7.250.108

- port: 5672

name: "main"

# => exports RABBITMQ_SERVICE_PORT_MAIN=5672

- port: 15672

name: "ui"

# => exports RABBITMQ_SERVICE_PORT_UI=15672

E.g. 02-rabbitmq.yml

23 of 31

Deploy to K8S

3/3

gcloud beta container clusters get-credentials "crawler-prod" \

--region "us-central1" --project "algolia-crawler"

DOCKER_IMAGE="gcr.io/algolia-crawler/crawler:latest"

docker build . -t ${DOCKER_IMAGE}

gcloud auth configure-docker

docker push ${DOCKER_IMAGE}

kubectl apply -f ./

kubectl set image --record deployment/mgr mgr=${DOCKER_IMAGE}

kubectl set image --record deployment/web web=${DOCKER_IMAGE}

kubectl scale deployment mgr web --replicas=0

kubectl create -f db-migrate.yml

until kubectl logs jobs/db-migrate; kubectl logs jobs/$1 2>&1 | grep -e "Done" >/dev/null; do sleep 1 ; done

kubectl scale deployment mgr web --replicas=1

E.g. deploy script

24 of 31

Connect to a Database

Heroku: one click + one env var

GCP/K8S

  1. Create CloudSQL instance (not part of K8S)
  2. Create a service account, download its JSON key
  3. Create a db user and password
  4. Store the password as a k8s secret
  5. Write and apply cloudsql-proxy.yml
  6. In every instance yml: load secret and use $(CLOUDSQL_PROXY_SERVICE_HOST)

25 of 31

Environment variables

Heroku: web UI or CLI

Env. vars in K8S

  • Generated for every service
  • Defined in YAML files
  • Secret env. vars
  • Config maps
  • Injection

=> sed and templates = your friends

26 of 31

SSL certificate

1/3

Heroku: one click

27 of 31

SSL certificate

2/3

GCP: “not our business”

28 of 31

SSL certificate

3/3

cert-manager:

Current status

As this project is pre-1.0, we do not currently offer strong guarantees around our API stability.

29 of 31

Conclusion

Migrating our distributed system to K8S/GCP was hard

K8S requires more custom tooling

K8S documentation is not ideal

Opportunity to improve the reliability of our system

30 of 31

Next Steps

Better monitoring & troubleshooting tools

Auto-scaling of workers

Auto-renewal of SSL certificate

Zero-downtime deployment

31 of 31

Thank you.

Adrien JOLY, Software Engineer

Site Search Crawler

adrien.joly@algolia.com