Typically, application architecture was the monolithic design that contains Model-View-Controller (MVC) and every component within a single big binary. Monolithic has some benefits, such as less latency within components, all in one straightforward packaging, and being easy to deploy and test.
However, a monolithic design has some downsides because the binary will be getting bigger and bigger. You always need to take care of the side effects when adding or modifying the code, therefore, making release cycles longer.
Containers and Kubernetes give more flexibility in using microservices for your application. The microservices architecture is very simple that can be divided into some modules or some service classes with MVC together.
Each microservice provides Remote Procedure Call (RPC) using RESTful or some standard network APIs to other microservices. The benefit is that each microservice is independent. There are minimal side effects when adding or modifying the code. Release the cycle independently, so it perfectly fits with the Agile software development methodology and allows to reuse these microservices to construct another application that builds the microservices ecosystem.
Prepare the simple microservices program. In order to push and pull your microservices, please register to Docker Hub (https://hub.docker.com/) to create your free Docker Hub ID in advance:
Once you successfully log in to your Docker Hub ID, you will be redirected to your Dashboard page as follows:
Prepare both microservices and the Frontend WebUI as a Docker image. Then, deploy them using the Kubernetes replication controller and service.
$ cat entry.py from flask import Flask, request app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" @app.route("/power/<int:base>/<int:index>") def power(base, index): return "%d" % (base ** index) @app.route("/addition/<int:x>/<int:y>") def add(x, y): return "%d" % (x+y) @app.route("/substraction/<int:x>/<int:y>") def substract(x, y): return "%d" % (x-y) if __name__ == "__main__": app.run(host='0.0.0.0')
Dockerfile
as follows in order to build the Docker image:$ cat Dockerfile FROM ubuntu:14.04 # Update packages RUN apt-get update -y # Install Python Setuptools RUN apt-get install -y python-setuptools git telnet curl # Install pip RUN easy_install pip # Bundle app source ADD . /src WORKDIR /src # Add and install Python modules RUN pip install Flask # Expose EXPOSE 5000 # Run CMD ["python", "entry.py"]
docker build
command to build the Docker image as follows://name as "your_docker_hub_id/my-calc" $ sudo docker build -t hidetosaito/my-calc . Sending build context to Docker daemon 3.072 kB Step 1 : FROM ubuntu:14.04 ---> 6cc0fc2a5ee3 Step 2 : RUN apt-get update -y ---> Using cache (snip) Step 8 : EXPOSE 5000 ---> Running in 7c52f4bfe373 ---> 28f79bb7481f Removing intermediate container 7c52f4bfe373 Step 9 : CMD python entry.py ---> Running in 86b39c727572 ---> 20ae465bf036 Removing intermediate container 86b39c727572 Successfully built 20ae465bf036 //verity your image $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE hidetosaito/my-calc latest 20ae465bf036 19 seconds ago 284 MB ubuntu 14.04 6cc0fc2a5ee3 3 weeks ago 187.9 MB
docker login
command to log in to Docker Hub://type your username, password and e-mail address in Docker hub $ sudo docker login Username: hidetosaito Password: Email: [email protected] WARNING: login credentials saved in /home/ec2-user/.docker/config.json Login Succeeded
docker push
command to register to your Docker Hub repository as follows://push to your docker index $ sudo docker push hidetosaito/my-calc The push refers to a repository [docker.io/hidetosaito/my-calc] (len: 1) 20ae465bf036: Pushed (snip) 92ec6d044cb3: Pushed latest: digest: sha256:203b81c5a238e228c154e0b53a58e60e6eb3d1563293483ce58f48351031a474 size: 19151
On accessing Docker Hub, you can see your microservices in the repository:
import os import httplib from flask import Flask, request, render_template app = Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/add", methods=['POST']) def add(): # # from POST parameters # x = int(request.form['x']) y = int(request.form['y']) # # from Kubernetes Service(environment variables) # my_calc_host = os.environ['MY_CALC_SERVICE_SERVICE_HOST'] my_calc_port = os.environ['MY_CALC_SERVICE_SERVICE_PORT'] # # remote procedure call to MicroServices(my-calc) # client = httplib.HTTPConnection(my_calc_host, my_calc_port) client.request("GET", "/addition/%d/%d" % (x, y)) response = client.getresponse() result = response.read() return render_template('index.html', add_x=x, add_y=y, add_result=result) if __name__ == "__main__": app.debug = True app.run(host='0.0.0.0')
entry.py
will pass the parameter to the template (index.html
) to render the HTML:<html> <body> <div> <form method="post" action="/add"> <input type="text" name="x" size="2"/> <input type="text" name="y" size="2"/> <input type="submit" value="addition"/> </form> {% if add_result %} <p>Answer : {{ add_x }} + {{ add_y }} = {{ add_result }}</p> {% endif %} </div> </body> </html>
Dockerfile
is exactly the same as microservices. So, eventually, the file structure will be as follows. Note that index.html
is a template file; therefore, put it under the templates directory:/Dockerfile /entry.py /templates/index.html
//build frontend Webui image $ sudo docker build -t hidetosaito/my-frontend . //login to docker hub, if not login yet $ sudo docker login //push frontend webui image $ sudo docker push hidetosaito/my-frontend
Launch both microservices and the Frontend WebUI.
Microservices (my-calc
) uses the Kubernetes replication controller and service, but it needs to communicate to other pods only. In other words, there's no need to expose it to the outside Kubernetes network. Therefore, the service type is set as ClusterIP
:
# cat my-calc.yaml apiVersion: v1 kind: ReplicationController metadata: name: my-calc-rc spec: replicas: 2 selector: app: my-calc template: metadata: labels: app: my-calc spec: containers: - name: my-calc image: hidetosaito/my-calc --- apiVersion: v1 kind: Service metadata: name: my-calc-service spec: ports: - protocol: TCP port: 5000 type: ClusterIP selector: app: my-calc
Use the kubectl
command to load the my-calc
pods as follows:
$ sudo kubectl create -f my-calc.yaml replicationcontroller "my-calc-rc" created service "my-calc-service" created
Frontend WebUI also uses the replication controller and service, but it exposes the port (TCP port 30080
) in order to access it from an external web browser:
$ cat my-frontend.yaml apiVersion: v1 kind: ReplicationController metadata: name: my-frontend-rc spec: replicas: 2 selector: app: my-frontend template: metadata: labels: app: my-frontend spec: containers: - name: my-frontend image: hidetosaito/my-frontend --- apiVersion: v1 kind: Service metadata: name: my-frontend-service spec: ports: - protocol: TCP port: 5000 nodePort: 30080 type: NodePort selector: app: my-frontend $ sudo kubectl create -f my-frontend.yaml replicationcontroller "my-frontend-rc" created service "my-frontend-service" created
You have exposed your service to an external port on all the nodes in your cluster. If you want to expose this service to the external Internet, you may need to set up firewall rules for the service port(s) (TCP port 30080
) to serve traffic. Refer to http://releases.k8s.io/release-1.1/docs/user-guide/services-firewalls.md for more details.
Let's try to access my-frontend
using a web browser. You can access any Kubernetes node's IP address; specify the port number 30080
as follows:
When you click on the addition button, it will forward a parameter to microservices (my-calc
). Microservices compute the addition (yes, just an addition!) and then return the result back to the Frontend WebUI as follows:
So now, it is easy to adjust the number of replicas for the Frontend WebUI and microservices independently. For example, WebUI replicas range from 2 to 8 and microservice replicas range from 2 to 16.
Also, if there's a need to fix some bugs, for example, there's a frontend need to validate the input parameter to check whether it is numeric or string (yes, if you type string and then submit, it will show an error!); it will not affect the build and deploy the cycle against microservices:
In addition, if you want to add an additional microservice, for example, subtract microservices, you may need to create another Docker image and deploy with another replication controller and service, so it will be independent from the current microservices.
Then, you can keep accumulate your own microservices ecosystem to re-use for another application.