Zero Downtime Migrations

    Old Versions of customer and preferenceTo run this section you need to deploy the latest customer and preference versions, if you already have these old versions of customer and preference, deploy them again with latest sources following Deploy Microservices.
    1. cd recommendation/java/vertx
    2. oc apply -f <(istioctl kube-inject -f ) -n tutorial
    3. or
    4. kubectl apply -f <(istioctl kube-inject -f ../../kubernetes/PostgreSQL-deployment.yml) -n tutorial
    5. oc create -f -n tutorial
    6. or
    7. kubectl create -f ../../kubernetes/PostgreSQL-service.yml -n tutorial
    8. # Wait until PotsgresSQL deployed
    9. oc get pods -w -n tutorial
    10. or
    11. kubectl get pods -w -n tutorial

    Inspecting Database Changes

    I recommend that every time you deploy a new recommendation service version or you do a POST call, you inspect how the database (recommendation table) has changed.

    To do it, you just need to go inside into the postgres container and use psql tool.Open a new terminal window and run next commands:

    1. oc get pods
    2. or
    3. kubectl get pods
    4. NAME READY STATUS RESTARTS AGE
    5. customer-7dcd544ff9-5j6ml 2/2 Running 0 22m
    6. postgres-6cc7c8bbd5-jqw8r 2/2 Running 0 31m
    7. preference-v1-7f7ddf6c4-fhjtw 2/2 Running 0 21m
    8. kubectl exec -ti postgres-6cc7c8bbd5-jqw8r -c postgres /bin/bash

    Then, when you are inside the container, you can inspect database changes:

    1. psql -U admin recommendation
    2. recommendation=# select * from recommendation;
    3. id | name
    4. ----+--------------------------
    5. 1 | Star Wars: A New Hope
    6. 2 | Star Trek: First Contact
    7. (2 rows)

    To create recommendation v4, we need to change one line in pom.xml) of project, and change configuration part from vertx-maven-plugin:

    Before:

    pom.xml

    1. <configuration>
    2. <verticle>com.redhat.developer.demos.recommendation.RecommendationVerticle</verticle>
    3. </configuration>

    After:

    pom.xml

    1. <configuration>
    2. <verticle>com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle</verticle>
    3. </configuration>

    Docker build (if you have access to Docker daemon)

    1. mvn clean package
    2. docker build -t example/recommendation:v4 .
    3. docker images | grep recommendation
    4. example/recommendation v4 b23e37349009 1 minutes ago 438MB
    5. example/recommendation v2 c31e399a9628 5 minutes ago 438MB
    6. example/recommendation v1 f072978d9cf6 8 minutes ago 438MB
    We have a 4th Deployment to manage the v4 version of recommendation.
    1. oc apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v4.yml) -n tutorial
    2. or
    3. kubectl apply -f <(istioctl kube-inject -f ) -n tutorial
    4. kubectl get pods -w -n tutorial
    5. oc create -f ../../kubernetes/Service.yml
    6. or
    7. kubectl create -f ../../kubernetes/Service.yml

    OpenShift S2I strategy (if you DON’T have access to Docker daemon)

    1. mvn clean package -f recommendation/java/vertx
    2. oc new-app -l app=recommendation,version=v4 --name=recommendation-v4 --context-dir=recommendation/java/vertx -e JAEGER_SERVICE_NAME=recommendation JAEGER_ENDPOINT=http://jaeger-collector.istio-system.svc:14268/api/traces JAEGER_PROPAGATION=b3 JAEGER_SAMPLER_TYPE=const JAEGER_SAMPLER_PARAM=1 JAVA_OPTIONS='-Xms128m -Xmx256m -Djava.net.preferIPv4Stack=true' fabric8/s2i-java~https://github.com/redhat-developer-demos/istio-tutorial -o yaml > recommendation-v4.yml
    3. oc apply -f <(istioctl kube-inject -f recommendation-v4.yml) -n tutorial
    4. oc cancel-build bc/recommendation-v4 -n tutorial
    5. oc delete svc/recommendation-v4 -n tutorial
    6. oc start-build recommendation-v4 --from-dir=. --follow -n tutorial

    Wait for v4 to be deployed

    Wait for those pods to show "2/2", the istio-proxy/envoy sidecar is part of that pod

    1. NAME READY STATUS RESTARTS AGE
    2. customer-3600192384-fpljb 2/2 Running 0 17m
    3. preference-243057078-8c5hz 2/2 Running 0 15m

    and test the customer endpoint

    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v4 [Star Wars: A New Hope,Star Trek: First Contact] from '60483540-9snd9': 1

    What you are getting here is a list of recommendations (every time will be the same) which are selected from the database, so it means that any change on the database is reflected to this query.

    For example, run next command to add a new recommendation:

    1. curl -H "Content-type: text/plain" -X POST --data "Avengers: Infinity War" customer-tutorial.$(minishift ip).nip.io
    2. 3

    And make a new request:

    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v4 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War] from '60483540-9snd9': 2

    What we want to do is one of the most complex operations in terms of database migration, and this is changing the name of a column.The same process is used for example if you need to change a data type, and it is similar in case of having to add new columns or tables.

    Flyway

    Flyway is a version control for your database.It allows you to evolute your database schema (and data) with ease and pleasure.

    Flyway allows you to have your database migration files altogether with your code.In summary, any SQL file placed following Flyway_ naming convention at src/main/resources/db/migration are executed against a configured database if it has not applied before.

    And this is the solution used in the recommendation service for dealing with database migrations.

    The steps we are going to follow for the migration is by doing 3 versions of the service:

    For sake of simplicity we are going to change the major number of the version, but in a real case, you’ll make it using minor/patch versions.
    • Add a new column and make the new service read from the old column and write to both.

    • Copy old data to the new column and code reads from the new column and writes to both.

    • The code reads and writes from the new column.

    You can create subversions (i.e one version for adding the column, another for making the new service read from old column and write to both) depending on your release process.

    So let’s see the process of updating the column named name to movie_name.

    Recommendation:v5

    The first thing to do before releasing recommendation v5 is to add the new column (which would be the final name, in this case, movie_name).To do it execute next command on a terminal:

    1. cp src/main/sql/V2__Add_movie_column_in_recommendation_table.sql src/main/resources/db/migration/V2__Add_movie_column_in_recommendation_table.sql

    And then update the recommendation service to a newer version and to write new values to both columns.

    1. private static final String RESPONSE_STRING_FORMAT = "recommendation v5 from '%s': %d\n";

    Open RecommendationPersistenceVerticle class and change start method form:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. router.post("/").handler(this::addRecommendation);
    2. // router.post("/").handler(this::addRecommendationToBothColumns);

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. // router.post("/").handler(this::addRecommendation);
    2. router.post("/").handler(this::addRecommendationToBothColumns);

    And then let’s build the new version and deploy it to the cluster.

    Docker build (if you have access to Docker daemon)

    1. mvn clean package
    2. docker build -t example/recommendation:v5 .
    3. docker images | grep recommendation
    4. example/recommendation v5 a84563734376 1 minutes ago 438MB
    5. example/recommendation v4 b23e37349009 1 minutes ago 438MB
    6. example/recommendation v2 c31e399a9628 5 minutes ago 438MB
    7. example/recommendation v1 f072978d9cf6 8 minutes ago 438MB
    We have a 5th Deployment to manage the v4 version of recommendation.
    1. oc apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v5.yml) -n tutorial
    2. oc get pods -w -n tutorial
    3. or
    4. kubectl apply -f <(istioctl kube-inject -f ) -n tutorial
    5. kubectl get pods -w -n tutorial

    OpenShift S2I strategy (if you DON’T have access to Docker daemon)

    1. mvn clean package -f recommendation/java/vertx
    2. oc new-app -l app=recommendation,version=v5 --name=recommendation-v5 --context-dir=recommendation/java/vertx -e JAEGER_SERVICE_NAME=recommendation JAEGER_ENDPOINT=http://jaeger-collector.istio-system.svc:14268/api/traces JAEGER_PROPAGATION=b3 JAEGER_SAMPLER_TYPE=const JAEGER_SAMPLER_PARAM=1 JAVA_OPTIONS='-Xms128m -Xmx256m -Djava.net.preferIPv4Stack=true' fabric8/s2i-java~https://github.com/redhat-developer-demos/istio-tutorial -o yaml > recommendation-v5.yml
    3. oc apply -f <(istioctl kube-inject -f recommendation-v5.yml) -n tutorial
    4. oc cancel-build bc/recommendation-v5 -n tutorial
    5. oc delete svc/recommendation-v5 -n tutorial
    6. oc start-build recommendation-v5 --from-dir=. --follow -n tutorial

    Wait for v5 to be deployed

    Wait for those pods to show "2/2", the istio-proxy/envoy sidecar is part of that pod

    1. NAME READY STATUS RESTARTS AGE
    2. customer-3600192384-fpljb 2/2 Running 0 17m
    3. preference-243057078-8c5hz 2/2 Running 0 15m
    4. recommendation-v4-60483540-9snd9 2/2 Running 0 12m
    5. recommendation-v5-00979354-89fga 2/2 Running 0 11m

    Blue-Green Deployment between v4 and v5

    Now we can start the release process of recommendation v5.Let’s redirect all traffic to v4.

    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v4 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War] from '60483540-9snd9': 2

    Now, let’s redirect traffic to v5:

    1. istioctl replace -f -n tutorial

    And apply some requests:

    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v5 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War] from '00979354-89fga': 1
    3. curl -H "Content-type: text/plain" -X POST --data "Frozen" customer-tutorial.$(minishift ip).nip.io
    4. 3
    5. curl customer-tutorial.$(minishift ip).nip.io
    6. recommendation v5 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen] from '00979354-89fga': 2

    What’s happening if we find some problems in v5 ? We just need to redirect traffic back to v4.

    1. istioctl replace -f istiofiles/virtual-service-recommendation-v4.yml -n tutorial
    1. recommendation v4 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen] from '60483540-9snd9': 3

    Notice that even though, it is the old version, the content added during v5 was released is still available and no data lost has ocurred.Next step (executed by DB administrator) could be just removing the movie_name column.

    But let’s suppose that v5 has no errors and it is going to be the correct release:

    1. istioctl replace -f -n tutorial

    And we can undeploy the recommendation v4 service from the cluster:

    1. oc delete all -l app=recommendation,version=v4
    2. or
    3. kubectl delete all -l app=recommendation,version=v4

    The first thing to do before releasing recommendation v6 is to copy old data (name column) to the new column (movie_name).Usually, if the column contains a lot of entries, you’ll do it using a batch process, but for the sake of simplicity, a simple SQL update is executed in this case.To do it execute next command on a terminal:

    1. cp src/main/sql/V3__Update_recommendation_data.sql src/main/resources/db/migration/V3__Update_recommendation_data.sql

    And then update code so the reads are happening from the new column and write to both.

    1. private static final String RESPONSE_STRING_FORMAT = "recommendation v6 from '%s': %d\n";

    Open RecommendationPersistenceVerticle class and change getRecommendationsFromDb method form:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. final List<String> recommendations = findRecommendation(resultSet);
    2. // final List<String> recommendations = findRecommendationNew(resultSet);

    to

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. // final List<String> recommendations = findRecommendation(resultSet);
    2. final List<String> recommendations = findRecommendationNew(resultSet);

    And then let’s build the new version and deploy it to the cluster.

    Docker build (if you have access to Docker daemon)

    1. mvn clean package
    2. docker build -t example/recommendation:v6 .
    3. example/recommendation v6 345bb6773434 1 minutes ago 438MB
    4. example/recommendation v5 a84563734376 1 minutes ago 438MB
    5. example/recommendation v4 b23e37349009 1 minutes ago 438MB
    6. example/recommendation v2 c31e399a9628 5 minutes ago 438MB
    7. example/recommendation v1 f072978d9cf6 8 minutes ago 438MB
    We have a 6th Deployment to manage the v4 version of recommendation.
    1. oc apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v6.yml) -n tutorial
    2. oc get pods -w -n tutorial
    3. or
    4. kubectl apply -f <(istioctl kube-inject -f ) -n tutorial
    5. kubectl get pods -w -n tutorial

    OpenShift S2I strategy (if you DON’T have access to Docker daemon)

    1. mvn clean package -f recommendation/java/vertx
    2. oc new-app -l app=recommendation,version=v6 --name=recommendation-v6 --context-dir=recommendation/java/vertx -e JAEGER_SERVICE_NAME=recommendation JAEGER_ENDPOINT=http://jaeger-collector.istio-system.svc:14268/api/traces JAEGER_PROPAGATION=b3 JAEGER_SAMPLER_TYPE=const JAEGER_SAMPLER_PARAM=1 JAVA_OPTIONS='-Xms128m -Xmx256m -Djava.net.preferIPv4Stack=true' fabric8/s2i-java~https://github.com/redhat-developer-demos/istio-tutorial -o yaml > recommendation-v6.yml
    3. oc apply -f <(istioctl kube-inject -f recommendation-v6.yml) -n tutorial
    4. oc cancel-build bc/recommendation-v6 -n tutorial
    5. oc delete svc/recommendation-v6 -n tutorial
    6. oc start-build recommendation-v6 --from-dir=. --follow -n tutorial

    Wait for v6 to be deployed

    Wait for those pods to show "2/2", the istio-proxy/envoy sidecar is part of that pod

    1. NAME READY STATUS RESTARTS AGE
    2. customer-3600192384-fpljb 2/2 Running 0 17m
    3. preference-243057078-8c5hz 2/2 Running 0 15m
    4. recommendation-v5-00979354-89fga 2/2 Running 0 11m
    5. recommendation-v6-98b29894-64g45 2/2 Running 0 11m

    Blue-Green Deployment between v5 and v6

    Now, let’s redirect traffic to v6:

    1. istioctl replace -f -n tutorial

    And apply some requests:

    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v6 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen] from '98b29894-64g45': 3
    3. curl -H "Content-type: text/plain" -X POST --data "The Lord Of The Rings: The Fellowship of the Ring" customer-tutorial.$(minishift ip).nip.io
    4. 5
    5. curl customer-tutorial.$(minishift ip).nip.io
    6. recommendation v6 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring] from '98b29894-64g45': 2

    What’s happening if we find some problems in v6 ? We just need to redirect traffic back to v5.

    1. istioctl replace -f istiofiles/virtual-service-recommendation-v5.yml -n tutorial
    1. curl customer-tutorial.$(minishift ip).nip.io
    2. recommendation v5 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring] from '00979354-89fga': 3

    So again no data lost and rolling back the service has no consequences.

    But let’s suppose that v6 has no errors and it is going to be the correct release:

    1. istioctl replace -f -n tutorial

    And we can undeploy the recommendation v5 service from the cluster:

    1. oc delete all -l app=recommendation,version=v5
    2. or
    3. kubectl delete all -l app=recommendation,version=v5

    Recommendation:v7

    The last thing to do is just write every time to the new column movie_name and not both.Also, it implies removing the null constraint and add it to the new column.

    1. cp src/main/resources/db/migration/V4__Update_movie_name_constraints.sql

    Open RecommendationPersistenceVerticle class and change getRecommendationsFromDb method form:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. router.post("/").handler(this::addRecommendationToBothColumns);
    2. // router.post("/").handler(this::addRecommendationToNewColumn);

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    1. // router.post("/").handler(this::addRecommendationToBothColumns);
    2. router.post("/").handler(this::addRecommendationToNewColumn);

    And then let’s build the new version and deploy it to the cluster.

    Docker build (if you have access to Docker daemon)

    1. mvn clean package
    2. docker build -t example/recommendation:v7 .
    3. docker images | grep recommendation
    4. example/recommendation v7 f449867a2342 2 minutes ago 438MB
    5. example/recommendation v6 345bb6773434 3 minutes ago 438MB
    6. example/recommendation v5 a84563734376 8 minutes ago 438MB
    7. example/recommendation v4 b23e37349009 9 minutes ago 438MB
    8. example/recommendation v2 c31e399a9628 12 minutes ago 438MB
    9. example/recommendation v1 f072978d9cf6 15 minutes ago 438MB
    oc apply -f <(istioctl kube-inject -f ) -n tutorial
    oc get pods -w -n tutorial
    
    or
    
    kubectl apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v7.yml) -n tutorial
    kubectl get pods -w -n tutorial

    OpenShift S2I strategy (if you DON’T have access to Docker daemon)

    mvn clean package -f recommendation/java/vertx
    oc new-app -l app=recommendation,version=v7 --name=recommendation-v7 --context-dir=recommendation/java/vertx -e JAEGER_SERVICE_NAME=recommendation JAEGER_ENDPOINT=http://jaeger-collector.istio-system.svc:14268/api/traces JAEGER_PROPAGATION=b3 JAEGER_SAMPLER_TYPE=const JAEGER_SAMPLER_PARAM=1 JAVA_OPTIONS='-Xms128m -Xmx256m -Djava.net.preferIPv4Stack=true' fabric8/s2i-java~https://github.com/redhat-developer-demos/istio-tutorial -o yaml  > recommendation-v7.yml
    oc apply -f <(istioctl kube-inject -f recommendation-v7.yml) -n tutorial
    oc cancel-build bc/recommendation-v7 -n tutorial
    oc delete svc/recommendation-v7 -n tutorial
    oc start-build recommendation-v7 --from-dir=. --follow -n tutorial

    Wait for v7 to be deployed

    Wait for those pods to show "2/2", the istio-proxy/envoy sidecar is part of that pod

    NAME                                  READY     STATUS    RESTARTS   AGE
    customer-3600192384-fpljb             2/2       Running   0          17m
    preference-243057078-8c5hz           2/2       Running   0          15m
    recommendation-v6-98b29894-64g45     2/2       Running   0          11m
    recommendation-v7-bb452a56-45tg2     2/2       Running   0          10m

    Blue-Green Deployment between v6 and v7

    Now, let’s redirect traffic to v7:

    istioctl replace -f istiofiles/virtual-service-recommendation-v7.yml -n tutorial

    And apply some requests:

    curl customer-tutorial.$(minishift ip).nip.io
    recommendation v7 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring] from 'bb452a56-45tg2': 4
    
    curl -H "Content-type: text/plain" -X POST --data "Howl's Moving Castle" customer-tutorial.$(minishift ip).nip.io
    6
    
    curl customer-tutorial.$(minishift ip).nip.io
    recommendation v7 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring, Howl's Moving Castle] from 'bb452a56-45tg2': 5

    What’s happening if we find some problems in v7 ? We just need to redirect traffic back to v6.

    istioctl replace -f  -n tutorial
    curl customer-tutorial.$(minishift ip).nip.io
    
    curl customer-tutorial.$(minishift ip).nip.io
    recommendation v6 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring, Howl's Moving Castle] from '98b29894-64g45': 6
    
    curl -H "Content-type: text/plain" -X POST --data "The Pink Panther" customer-tutorial.$(minishift ip).nip.io
    7

    Now v7 is fixed and released again:

    istioctl replace -f istiofiles/virtual-service-recommendation-v7.yml -n tutorial
    curl customer-tutorial.$(minishift ip).nip.io
    recommendation v6 [Star Wars: A New Hope,Star Trek: First Contact, Avengers: Infinity War, Frozen, The Lord Of The Rings: The Fellowship of the Ring, Howl's Moving Castle, The Pink Panther] from '98b29894-64g45': 7

    So no data lose, every step we follow is safe, and at any time we are able to roll back to last known working version without any problem and when the new version is released again, everything still works.

    The final step that should be done by a DB administrator for example when maintenance window is set, and it is to delete the old column since the new version of the service is not using it anymore for reading nor for writing.Keep in mind that this is a destructive operation so, it should be taken with care.

    oc delete all -l app=recommendation
    or
    kubectl delete all -l app=recommendation
    
    oc delete all -l app=postgres
    or
    kubectl delete all -l app=postgres
    
    istioctl delete -f istiofiles/destination-rule-recommendation-v4-v5-v6-v7.yml -n tutorial
    istioctl create -f istiofiles/virtual-service-recommendation-v7.yml -n tutorial

    Then let’s redeploy recommendation v1 and v2.

    cd recommendation/java/vertx
    
    oc apply -f <(istioctl kube-inject -f ) -n tutorial
    or
    kubectl apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v1.yml) -n tutorial
    
    oc apply -f <(istioctl kube-inject -f ) -n tutorial
    or
    kubectl apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v2.yml) -n tutorial
    
    oc create -f 
    oc get pods -w
    or
    kubectl create -f ../../kubernetes/Service.yml
    kubectl get pods -w

    If you want to rollback the code of recommendation service to initial stages, you need to do next changes:

    • Rollback code changes in RecommendationPersistenceVerticle class.

    From:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    // final List<String> recommendations = findRecommendation(resultSet);
    final List<String> recommendations = findRecommendationNew(resultSet);

    To:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    final List<String> recommendations = findRecommendation(resultSet);
    // final List<String> recommendations = findRecommendationNew(resultSet);

    And from:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    // router.post("/").handler(this::addRecommendation);
    // router.post("/").handler(this::addRecommendationToBothColumns);
    router.post("/").handler(this::addRecommendationToNewColumn);

    To:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    router.post("/").handler(this::addRecommendation);
    router.post("/").handler(this::addRecommendationToBothColumns);
    // router.post("/").handler(this::addRecommendationToNewColumn);

    And from:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    private static final String RESPONSE_STRING_FORMAT = "recommendation v7 from '%s': %d\n";

    To:

    com.redhat.developer.demos.recommendation.RecommendationPersistenceVerticle

    private static final String RESPONSE_STRING_FORMAT = "recommendation v4 from '%s': %d\n";
    • Remove next SQL scripts:
    rm src/main/resources/db/migration/V2__Add_movie_column_in_recommendation_table.sql
    rm src/main/resources/db/migration/V3__Update_recommendation_data.sql
    rm src/main/resources/db/migration/V4__Update_movie_name_constraints.sql
    • Set correct verticle:

    From:

    pom.xml

    
    

    To:

    pom.xml