Dictator App is a demo application for demonstrating Blue/Green deployment of an application that uses ActiveMQ. It helps autocratic regimes to censor news articles.
The app consists of three components
- NewsEndpoint - REST Endpoint (
POST /api/news/article
) that receives new articles in format{"title":"Something", "content":"Some content"}
and posts it to theArticleSubmissions
queue for further processing byCensorshipService
. - CensorshipService - Processes the
ArticleSubmissions
queue, looks at the content of the articles and decides whether it will mark the article asOK
orCENSORED
before it sends them further.- If article is
OK
it goes to toPublishedArticles
queue. - If article is
CENSORED
it goes to toCensoredArticles
topic.
- If article is
- PublishingService - Processes the
PublishedArticles
queue and logs the article content toSTDOUT
- PressMonitoringService - Processes the
CensoredArticles
topic and logs the article title and censorship date toSTDOUT
Run bin/build-docker-app.sh
to create a docker image dictator-app:<version>
.
Run bin/build-docker-mq.sh
to create a docker image dictator-activemq:<version>
export ARTEMIS_DATA_DIR=/tmp/artemis1
bin/run-docker-mq.sh 61616 8161 5445 5672 1883 61613 172.17.0.3 61617
export ARTEMIS_DATA_DIR=/tmp/artemis2
bin/run-docker-mq.sh 61617 8162 5446 5673 1884 61618 172.17.0.2 61616
- NOTE: Change
ARTEMIS_DATA_DIR
value for something that makes sense for your system. - NOTE: assign target IPs based on your docker env
bin/run-docker-app.sh 8080 172.17.0.2 61616
bin/run-docker-app.sh 8081 172.17.0.3 61617
- NOTE: assign target IPs based on your docker env
GET the official propaganda article
curl -H "Content-type: application/json" http://localhost:8080/api/news/article
POST an article
curl -d '{"title":"hello", "content":"a message"}' -H "Content-type: application/json" http://localhost:8080/api/news/article
bin/logs.sh
- Show logs of- app
- candidate app
- blue MQ
- green MQ
bin/check-queues.sh
- Show message counts in queues and topicsbin/check-bridges.sh
- Show bridges and their statesbin/create-bridge.sh
- Create bridges for all of the queues/topics in- blue MQ
- green MQ
bin/destroy-bridge.sh
- Create bridges for all of the queues/topics in- blue MQ
- green MQ
bin/kube-apply.sh
- Initial deployment of Kubernetes objects to Google Cloudbin/kube-deploy-candidate.sh
- Deploy currently checked out app version (make sure you've built it)bin/kube-promote-candidate.sh
- Switchdictator-app
service to new version and delete deployment of old versionbin/kube-info.sh
- Print which MQ server is configured with current deploymentbin/kube-port-forward.sh
- Port-forward to localhost- blue MQ
- green MQ
bin/mq-admin.sh
- Run Java MQ Admin (the tool must be built withbin/build-admin.sh
)bin/build-admin.sh
- Builds the Java MQ Admin tool (basically not needed)bin/pause.sh
- Pause/resume processing of these addressess: submitted,published and censored- USAGE:
bin/pause.sh {b|g} {submitted|published|censored} {p|r}
- pause/resume given address, in blue or green MQbin/pause.sh {b|g}
- Show paused status in given MQ
- USAGE:
bin/build-docker-app.sh
- Maven build the app, create docker image and push it into the GCR repository, uses current git tagbin/build-docker-mq.sh
- Create Artemis ActiveMQ docker image and push it into the GCR repository, uses current git tagbin/run-docker-app.sh
- Runs current tag of app in local dockerbin/run-docker-mq.sh
- Runs current tag of MQ server in local dockerbin/post.sh
- Posts new article to the app via it's REST API- USAGE:
bin/post.sh "Article TITLE" "Article CONTENT"
- NOTE: if article content contains string
dictator is corrupt
then it will be censored - NOTE: if article content equals
candidate
then it's posted todictator-app-candidate
endpoint instead ofdictator-app
- NOTE: if article content equals
loop
then it will be reposted every second until the script is killed
- USAGE:
- Create a test cluster, either manually or using
gcloud
CLI, e.g:
gcloud container clusters create learning-cluster \
--num-nodes 4 \
--machine-type g1-small \
--zone europe-west1-c
gcloud container clusters get-credentials learning-cluster
-
Copy
build.conf.example
tobuild.conf
and fill in your values. -
Create Kubernetes objects
bin/kube-apply.sh
-
Manual: Change load-balancer IP to static and set your DNS so that the address e.g.
example.com
inAPP_DOMAIN
(in build.conf) points to it -
If your address is
example.com
, you should be able to access these services:
- You can check status of your services using
bin/kube-info.sh
After deployment of the first version 1.0
you'll have the following scenario
Deploy candidate (version 2.0
). This can be done using the script
bin/kube-deploy-candidate.sh
You'll end up with something like this
This creates a separate service dictator-app-candidate
which you can run End-to-end tests against. This will be a new pod configured with completely separate MQ server dictator-mq-g
After you checked that the version 2.0
behaves correctly (with it's independent MQ dictator-mq-g
), you can remove the old version and direct all of the production traffic to the new pod.
After this you'll also gracefully shutdown version 1.0
pod. This may leave some unprocessed messages in it's MQ server dictator-mq-b
.
You can promote candidate using
bin/kube-promote-candidate.sh
The unprocessed messages can now be fed via bridge to the new active MQ server dictator-mq-g
. There's N-1 compatibility between versions 1.0
and 2.0
messages, i.e. version 2.0
app
can process version 1.0
messages.
After all of the messages are transferred via the ActiveMQ Core bridge, the bridge can be shut down.
To create and destroy bridges, you can use these commands
bin/create-bridge.sh b
bin/destroy-bridge.sh b
- NOTE: These will create and destroy bridge
dictator-mq-b
->dictator-mq-g
. Use letterg
instead ofb
to create and destroy the opposite direction bridge. These work with ActiveMQ Artemis Jolokia REST API.
Before we can switch roles of dictator-mq-b
and dictator-mq-g
and deploy another version, we need to wait until all of the 1.0
messages are processed, so that we don't need to maintain
compatibility between another version and this one. You can check the status with bin/check-queues.sh
command.
bin/logs.sh
- Show logs of- app
- candidate app
- blue MQ
- green MQ
bin/check-queues
- Show message counts in queues and topics- `
git tag <new_version>
# This will create new pod and point dictator-app-candidate service to it:
bin/kube-deploy-candidate.sh
# Run e2e tests: this posts to the candidate URL:
bin/post.sh test candidate
# <<OPTIONAL>> Pause production traffic to get some messages that will need to migrate and be processed by new version
bin/pause.sh <active-mq> published p
bin/pause.sh <active-mq> censored p
bin/post.sh test1 "safe content"
bin/post.sh test2 "safe content"
bin/post.sh test3 "safe content"
bin/post.sh test1 "dictator is corrupt"
bin/post.sh test2 "dictator is corrupt"
bin/post.sh test3 "dictator is corrupt"
# <<OPTIONAL>>
# After you're done with test, promote the candidate, this will delete the old version
bin/kube-promote-candidate.sh
# <<OPTIONAL>> Now resume the traffic in the now inactive mq
bin/pause.sh <inactive-mq> published r
bin/pause.sh <inactive-mq> censored r
# <<OPTIONAL>>
# To find out which MQ is used by active app:
bin/kube-info.sh
# Create bridge on the mq that is now NOT active, so that it pushes messages to the active one
bin/create-bridge.sh <inactive-mq>
# To check the message transfer progress
bin/check-queues.sh
# Then destroy the bridge
bin/destroy-bridge.sh <inactive-mq>
# Now check how your active app deals with the old messages and when they're consumed, you ready to repeat the cycle
bin/check-queues.sh
When you need to update the MQ server, you usually want to do something like
- update the ActiveMQ Artemis version
- change ActiveMQ configuration
- add a topic or a queue
- remove a topic or a queue
You start in a state where you have a running app using the ACTIVE (blue or green) MQ server. To ensure an uninterrupted traffic, you simply update the INACTIVE MQ server.
# To find out which MQ is used by active app:
bin/kube-info.sh
# Now just replace the inactive MQ server with newer version
bin/update-mq.sh
In your next B/G deployment cycle you'll start using this server, so make sure your new App version is compatible with the configuration.
Your N+1 version needs to process/discard messages that were sent to it by the old version.
The old bridging scripts will still create bridge for the old queues After migration you can also remove the queue from scripts that create bridges.
The old version will not produce any data to this new queue, so no special action needed here. Add the queue to the list of bridges in create-bridge scripts.
This can be handled by bridges, configure them to forward to a newly named address
When you're changing format of message serialization, you need to respect N-1 compatibility between versions, i.e. the new version must be able to process messages of old version.
Let's explain the problem on the example of JSON object serialization format used in our application.
When you add a field, you must be prepared that the old version won't fill it and you need to expect null
value even when your new code fills it everywhere.
When you remove a field, you need to make sure that the old version doesn't send any important data to it, because it will be ignored when serialized to your new object that doesn't declare it.