<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Applied Architect]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>http://www.theappliedarchitect.com/</link><image><url>http://www.theappliedarchitect.com/favicon.png</url><title>The Applied Architect</title><link>http://www.theappliedarchitect.com/</link></image><generator>Ghost 4.1</generator><lastBuildDate>Wed, 29 Apr 2026 11:38:24 GMT</lastBuildDate><atom:link href="http://www.theappliedarchitect.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Leveraging the SR04 sonar module with a Raspberry Pi]]></title><description><![CDATA[In this blog post, we will explore how to use the SR04 module with a Raspberry Pi.]]></description><link>http://www.theappliedarchitect.com/leveraging-the-sr04-sonar-module-with-a-raspberry-pi/</link><guid isPermaLink="false">6410dcd0d98ea200018a700b</guid><category><![CDATA[robotics]]></category><category><![CDATA[rpi]]></category><category><![CDATA[programming]]></category><category><![CDATA[sensors]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Tue, 14 Mar 2023 21:25:47 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2023/03/diana-polekhina-iUfusOthmgQ-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2023/03/diana-polekhina-iUfusOthmgQ-unsplash.jpg" alt="Leveraging the SR04 sonar module with a Raspberry Pi"><p>The SR04 module is a popular ultrasonic distance sensor that is commonly used with microcontrollers like Arduino and Raspberry Pi. It can be used to measure distances from 2cm to 400cm with an accuracy of 0.3cm. In this blog post, we will explore how to use the SR04 module with a Raspberry Pi.</p><h2 id="hc-sr04-module">HC-SR04 Module</h2><p>The HC-SR04 module consists of a transmitter and receiver, and works by sending out a high-frequency sound pulse and timing how long it takes for the pulse to bounce back off an object and return to the sensor. By knowing the speed of sound and the time it takes for the pulse to return, the module can calculate the distance between itself and the object.</p><h2 id="materials">Materials</h2><p>Before starting the project, you will need to gather the following materials:</p><ul><li>Raspberry Pi (any model) - <a href="https://amzn.to/3mQmGXs">Amazon link</a></li><li>HC-SR04 sensor - <a href="https://amzn.to/3FjSBGr">Amazon link</a></li><li>Breadboard (optional)</li><li>Female-to-female jumper wires - <a href="https://amzn.to/3Fn3RBU">Amazon link</a></li></ul><h2 id="making-the-connections">Making the Connections</h2><p>The SR04 sensor has four pins: VCC, GND, Trig, and Echo. The VCC and GND pins are used to provide power to the sensor. The Trig pin is used to send the ultrasonic pulse, and the Echo pin is used to receive the reflected signal. In this tutorial we will use GPIO14 (board pin 8) for the TRIG pin and GPIO15 (board pin 10) for the ECHO as seen in this diagram:</p><!--kg-card-begin: html--><img srcset="https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-sr04.jpg 300w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-sr04.jpg 600w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-sr04.jpg 1000w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-sr04.jpg 2000w" sizes="(max-width: 800px) 400px,
                        (max-width: 1170px) 1170px,
                            2000px" src="https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-sr04.jpg" alt="Leveraging the SR04 sonar module with a Raspberry Pi"><!--kg-card-end: html--><h2 id="writing-the-python-code">Writing the Python code</h2><p>With the SR04 module connected and the required packages installed, we&apos;re ready to write a Python script to read the distance measurements from the sensor. </p><pre><code class="language-Python">import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)

trig_pin = 8  # GPIO14 pin connected to the Trig pin on SR04 module
echo_pin = 10  # GPIO15 pin connected to the Echo pin on SR04 module

GPIO.setup(trig_pin, GPIO.OUT)
GPIO.setup(echo_pin, GPIO.IN)

def distance():
    # Send a 10us pulse to trigger the SR04 module
    GPIO.output(trig_pin, True)
    time.sleep(0.00001)
    GPIO.output(trig_pin, False)
    
    # Measure the duration of the pulse from the Echo pin
    start_time = time.time()
    while GPIO.input(echo_pin) == 0:
        start_time = time.time()
        
    end_time = time.time()
    while GPIO.input(echo_pin) == 1:
        end_time = time.time()
        
    # Calculate the distance based on the duration of the pulse
    duration = end_time - start_time
    distance = duration * 17150  # speed of sound in cm/s
    distance = round(distance, 2)  # round to two decimal places
    
    return distance

# Main loop
try:
    while True:
        dist = distance()
        print(f&quot;Distance: {dist} cm&quot;)
        time.sleep(1)
        
except KeyboardInterrupt:
    GPIO.cleanup()</code></pre><ol><li>We first import the RPi.GPIO library and the time module. We also set the GPIO mode to <code>BOARD</code>, which means we refer to the GPIO pins by their physical pin numbers on the Pi board.</li><li>We define the GPIO pins connected to the Trig and Echo pins on the SR04 module.</li><li>We set up the Trig pin as an output pin and the Echo pin as an input pin.</li><li>We define a function <code>distance()</code> that sends a 10us pulse to the Trig pin to trigger the SR04 module. It then measures the duration of the pulse from the Echo pin and calculates the distance based on the speed of sound.</li><li>In the main loop, we call the <code>distance()</code> function and print the distance to the console. We also add a delay of 1 second between measurements.</li><li>We handle the <code>KeyboardInterrupt</code> exception by cleaning up the GPIO pins.</li></ol><h2 id="run-the-code">Run the code</h2><p>After running your file you should see the distance measurements printed to the console every second. Note that you may need to adjust the GPIO pin numbers to match the pins you have connected to the SR04 module on your Pi.</p><h2 id="conclusion">Conclusion</h2><p>In this tutorial, we learned how to leverage an SR04 sonar sensor module using a Raspberry Pi and control it using Python code. We hope this tutorial was helpful and inspires you to build your own robotics projects using Raspberry Pi.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[Controlling an MG995 servo with a Pi]]></title><description><![CDATA[In this tutorial, we will explore how to connect a Raspberry Pi with an MG995 servo motor.]]></description><link>http://www.theappliedarchitect.com/controlling-mg995-using-a/</link><guid isPermaLink="false">640f3624d98ea200018a6f5f</guid><category><![CDATA[robotics]]></category><category><![CDATA[rpi]]></category><category><![CDATA[programming]]></category><category><![CDATA[servo]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Mon, 13 Mar 2023 17:21:47 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2023/03/harrison-broadbent-raLeFIxXgDY-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2023/03/harrison-broadbent-raLeFIxXgDY-unsplash-1.jpg" alt="Controlling an MG995 servo with a Pi"><p>Raspberry Pi is a credit card-sized computer that can be used for various DIY projects, including robotics. One of the essential components of robotics is a servo motor, which can be used for a variety of purposes such as moving an arm, controlling a camera, or steering a robot. In this tutorial, we will explore how to connect a Raspberry Pi with an MG995 servo motor.</p><h2 id="the-mg995r">The MG995(R)</h2><p>The MG995 servo is a type of motorized actuator that is commonly used in robotics and automation projects. It is a high-torque servo motor that can rotate to a specific position based on the electrical signals it receives from a controller.</p><p>The MG995 servo operates on a pulse-width modulation (PWM) signal, which is a type of digital signal that is commonly used to control motors and other devices. By varying the width of the PWM signal, the servo can be made to rotate to different positions, allowing for precise control over its movement. We will control the length of this signal with our Raspberry Pi.</p><h2 id="materials">Materials</h2><p>Before starting the project, you will need to gather the following materials:</p><ul><li>Raspberry Pi (any model) - <a href="https://amzn.to/3mQmGXs">Amazon link</a></li><li>MG995 Servo Motor - <a href="https://amzn.to/3FjSBGr">Amazon link</a></li><li>Breadboard (optional)</li><li>Male-to-female jumper wires - <a href="https://amzn.to/3Fn3RBU">Amazon link</a></li></ul><h2 id="making-the-connections">Making the connections</h2><p> The MG995 servo motor has three wires: red (power), brown (ground), and orange (signal). To connect the servo motor to the Raspberry Pi, follow these steps:</p><ul><li>Connect the red wire of the servo motor to the 5V power source.</li><li>Connect the brown wire of the servo motor to the ground of the Raspberry Pi.</li><li>Connect the orange wire of the servo motor to a GPIO pin 13 of the Raspberry Pi.</li></ul><!--kg-card-begin: html--><img srcset="https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-mg996.jpg 300w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-mg996.jpg 600w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-mg996.jpg 1000w,
                            https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-mg996.jpg 2000w" sizes="(max-width: 800px) 400px,
                        (max-width: 1170px) 1170px,
                            2000px" src="https://storage.googleapis.com/theappliedarchitect/Controlling-an-MG995-servo-with-a-Pi/rpi-mg996.jpg" alt="Controlling an MG995 servo with a Pi"><!--kg-card-end: html--><h2 id="writing-the-python-code">Writing the Python code</h2><p>Now that we have connected the servo motor and installed the required libraries, we can write the Python code to control the servo motor. Open a new file in the terminal window and type the following:</p><pre><code class="language-Python">import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(13, GPIO.OUT)

p = GPIO.PWM(13, 50)  # PWM frequency is 50Hz
p.start(2.5)  # Initialization

try:
    while True:
        p.ChangeDutyCycle(5)  # Rotate the servo motor to 90 degrees
        time.sleep(1)
        p.ChangeDutyCycle(10)  # Rotate the servo motor to 180 degrees
        time.sleep(1)
except KeyboardInterrupt:
    p.stop()
    GPIO.cleanup()</code></pre><p>In the code above, we first import the necessary libraries and set the GPIO mode to BCM. We then set up GPIO pin 13 as an output pin and initialize the PWM signal with a frequency of 50Hz and a duty cycle of 2.5 (which is equivalent to 0 degrees).</p><p>We then enter a while loop that will rotate the servo motor to 90 degrees and then 180 degrees with a delay of one second between each rotation. Finally, we handle the KeyboardInterrupt exception and stop the PWM signal and clean up the GPIO pins.</p><h2 id="run-the-code">Run the code</h2><p>After running your file you should see the servo motor should start rotating to 90 degrees and then 180 degrees with a delay of one second between each rotation.</p><h2 id="conclusion">Conclusion</h2><p>In this tutorial, we learned how to connect an MG995 servo motor to a Raspberry Pi and control it using Python code. We hope this tutorial was helpful and inspires you to build your own robotics projects using Raspberry Pi.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[Deploying React in Production Mode with NGINX]]></title><description><![CDATA[A quick how-to guide on deploying a react application in production mode with the NGINX proxy.]]></description><link>http://www.theappliedarchitect.com/deploying-react-in-production/</link><guid isPermaLink="false">6257216cd98ea200018a6bd5</guid><category><![CDATA[react]]></category><category><![CDATA[nginx]]></category><category><![CDATA[node.js]]></category><category><![CDATA[webapp]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Thu, 21 Apr 2022 19:30:08 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2022/04/react-image2-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2022/04/react-image2-1.jpg" alt="Deploying React in Production Mode with NGINX"><p>Getting started with react takes minutes but production deployments (although quick) can be intimidating. This post documents the step-by-step deployment using Ubuntu 18.04 LTS and <a href="https://www.nginx.com/">NGINX</a>.</p><h3 id="objectives">Objectives</h3><p><strong><strong>Time required: </strong></strong>10 minutes</p><h3 id="prerequisites">Prerequisites</h3><p>To follow along, you should have the following:</p><ol><li><a href="https://reactjs.org/tutorial/tutorial.html">A basic understanding of how JavaScript and React</a></li><li>Somewhere to host your application (I will be using a GCP VM instance)</li></ol><p>Throughout the article the following domain is used mydomainname.com, this needs to be replaced with the domain name or IP of your server. For SSL and HTTPS setup <a href="https://theappliedarchitect.com/setting-up-https-for-your-blog-certbot/">this</a> guide can be followed.</p><hr><p><strong>Step 1 - build your application </strong></p><p>Before deploying the application you must build the project which produces a directory with an optimized codebase. This is contained within a <strong>build </strong>folder and encapsulates everything the application needs to run. If the application is deployed somewhere other than your development system you can either build the code locally and migrate the codebase and build on the deployment machine or migrate just the build directory.</p><!--kg-card-begin: markdown--><pre><code>cd project-folder
sudo npm install
sudo npm run build
</code></pre>
<!--kg-card-end: markdown--><p>This creates a <strong>build</strong> folder directly within the working directory. Before moving to the next step, let&apos;s relocate the folder to a typical deployment location <code>/var/www/</code>. Create this <code>/var/www/</code> folder if it does not exist and copy over the build directory.</p><pre><code>sudo mkdir /var/www/
sudo scp -r ./build/* /var/www/build/</code></pre><p>Next NGINX needs to be configured to serve this build.</p><p><strong>Step 2 - install and configure NGINX</strong></p><p>Install NGINX using the following command <code>sudo apt install nginx</code>. This creates an NGINX folder under <code>/etc/nginx/</code>. To host the web app, the default NGINX config file must be updated or a new one created. In this walkthrough, the existing default file is replaced. Change the content of the file <code>/etc/nginx/sites-enabled/</code> to the following:</p><pre><code>server {
    listen 0.0.0.0:80;
    server_name mydomainname.com;
    access_log /var/log/nginx/app.log;
    root /var/www/build;
    index index.html index.htm;
    try_files $uri /index.html;
    location / {
        try_files $uri $uri/ = 404;
    }
}</code></pre><p><strong>Note</strong>: <strong>/var/www/build </strong>must coincide with the location of the production build folder from the previous step.</p><p><strong>Note</strong>: <strong>server_name </strong>value ( <a href="mydomainname.com">mydomainname.com</a> above) must coincide with the server&apos;s access point. This can either be the IP address or domain name.</p><p><strong>Step 3 - deploy </strong></p><p>For the changes to take effect the NGINX service must be restart:</p><pre><code>sudo service nginx stop
sudo service nginx start</code></pre><p>The application should now be running and accessible using the server_name provided in the config file.</p><p><strong><strong>Good luck and happy coding!</strong></strong></p>]]></content:encoded></item><item><title><![CDATA[2D Rotorcopter Mechanics and PID Control with Unity]]></title><description><![CDATA[Simulating 2-dimensional flight and learning about PID control loops with help from the Unity Engine.]]></description><link>http://www.theappliedarchitect.com/learning-2d-rotorcopter-mechanics-and-control-with-unity/</link><guid isPermaLink="false">61f1ae2dd98ea200018a6ab1</guid><category><![CDATA[robotics]]></category><category><![CDATA[drone]]></category><category><![CDATA[simulation]]></category><category><![CDATA[quadcopter]]></category><category><![CDATA[PID]]></category><category><![CDATA[Unity]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Tue, 01 Feb 2022 03:49:17 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2022/02/alessio-soggetti-Cf5wnynAsiw-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2022/02/alessio-soggetti-Cf5wnynAsiw-unsplash.jpg" alt="2D Rotorcopter Mechanics and PID Control with Unity"><p>This post covers the implementation of a 2-dimensional quadcopter using the Unity game engine and PID controllers for flight control. This was a recent implementation and the details are documented here in hopes that others find some of this helpful.</p><h3 id="expected-outcomes">Expected Outcomes</h3><p>Follow the walkthrough in this tutorial should <em>hopefully</em> provide the following:</p><ol><li>A Unity environment simulating 2-dimensional quadcopter flight ready for experimentation.</li><li>A basic understanding of how PID controllers can be used in Unity (and elsewhere) as control systems.</li><li>Basics on rotorcraft 2-dimensional dynamics.</li></ol><p>All the covered code as well as the working project can be found in <a href="https://github.com/adidinchuk/2d-unity-quadcopter-sim" rel="noopener">this GitHub</a> repository. If you don&#x2019;t care about the walkthrough of how and why you can grab the code and skip right to <strong>The Simulation</strong> section.</p><h3 id="before-getting-started">Before getting started</h3><p>A few decisions were made for this simulation.</p><p><strong>Why Unity?</strong> <a href="https://unity.com/" rel="noopener">Unity</a> comes with a sophisticated physics engine and makes rapid development and experimentation quick and easy.</p><p><strong>Why Only 2 Dimensions? </strong>The problem of rotorcraft control is complex and removing a dimension allows for significant simplification. Once control in 2 dimensions is mastered, 3 dimensions can be tackled.</p><p><strong>Why the </strong><a href="https://en.wikipedia.org/wiki/PID_controller" rel="noopener"><strong>PID Controller</strong></a><strong>?</strong> Experienced Unity developers might wonder why use PID control over classic pathing techniques. The goal is to use a controller that translates to the real world. PID controllers are the go-to control loops for self-regulating systems.</p><h3 id="basics-of-a-quadcopter-in-2d">Basics of a Quadcopter in 2D</h3><p>By starting with simulation in 2 dimensions instead of 3, the complexity is significantly reduced.</p><p><strong><em>IMPORTANT:</em></strong><em> In this article, the vertical axis is referred to as </em><strong><em>y </em></strong><em>and the</em><strong><em> </em></strong><em>horizontal axis as</em><strong><em> x</em></strong><em>. This is done to keep consistency with the </em><strong><em>x-y</em></strong><em> 2D axis in Unity. In standard notation, the horizontal axis would be referred to as </em><strong><em>y</em></strong><em> and the vertical axis as </em><strong><em>z</em></strong><em> (or </em><strong><em>x =&gt; y</em></strong><em> and </em><strong><em>y =&gt; z</em></strong><em>).</em></p><p><strong>Active Forces</strong></p><p>The drone has 3 total forces acting on it at any given time:</p><ol><li>A variable upward force for <strong>each </strong>propeller perpendicular to the frame and upwards</li><li>A constant force of gravity directly downwards regardless of the frame&#x2019;s orientation.</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*2JzXS-XBHqWylg6zhRY3mA.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>Digram by&#xA0;Auther</figcaption></figure><p>As long as the quadcopter is defined as a rigid body with mass, Unity takes care of the gravitational force. The force (or thrust) of the rotors is what the control system has to manage.</p><p><strong>Control</strong></p><p>Acceleration of the simulated quadcopter can be controlled by changing the thrust of the rotors.</p><p>When the quadcopter is level with the ground, acceleration is only applied along the <strong>y</strong> axis. Increasing the thrust of the rotors in this orientation causes the quadcopter to accelerate up and decreasing thrust causes it to accelerate down.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*iaSssH8zS4sBRippcILbAQ.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>Digram by&#xA0;Auther</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*jJ20y45Ek-pMrza2KYC-_g.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>When the quadcopter is tilted at an angle (we will call this angle <strong>phi</strong> or <strong>&#x1D719;</strong>) relative to the ground the force from the propellers begins to apply both horizontal and vertical acceleration. Maintaining thrust in this orientation increases horizontal velocity (y-axis).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*g7Hqkvzo21XfiIoLJnEVZQ.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>Diagram by&#xA0;Author</figcaption></figure><p>or &#x2026;</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*sH4uMk7n4u0DP3xS1stLpg.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>Lastly, in order to change the angular orientation of the quadcopter (or <strong>roll</strong>), the thrust between the rotors needs to vary in order to produce torque (or <a href="https://en.wikipedia.org/wiki/Moment_%28physics%29" rel="noopener"><strong>moment</strong></a><strong>)</strong>. This moment is proportional to the difference between the forces and the distance between the rotors.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*gRlKutl_xLgv-rat6gN-WA.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>Diagram by&#xA0;Author</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*TXJ0-EeAyq8fU6mjDSI1Qw.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p><strong><em>NOTE:</em></strong><em> Quadcopters in 3 dimensions also need to control </em><strong><em>yaw, </em></strong><em>which is torque (or moment) arising from the motors spinning. Luckily with only 2D there is no third dimension for the drone to </em><strong><em>yaw </em></strong><em>around.</em></p><p>The dynamics above allow for a fairly simple control system where only the overall thrust for the quadcopter&#x2019;s linear acceleration and the difference in forces between the rotors for rotation need to be determined. This is typically expressed in terms of <strong>u1 </strong>and <strong>u2</strong>, where <strong>u1 </strong>dictates total thrust and <strong>u2 </strong>the total moment.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*Py98x3kO1fjOqvpScMvCWQ.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>These are the values that the PID controller needs to determine.</p><h3 id="the-control-system">The Control System</h3><p>The <a href="https://en.wikipedia.org/wiki/PID_controller" rel="noopener">PID controller</a> is a control loop approach that continuously supplies a system with an input (like thrust, voltage, resistance, etc.) and adjusts this input based on how well the system performs over time.</p><p>The general idea revolves around providing the PID controller with 3 coefficients&#x200A;&#x2014;&#x200A;Position, Integral, and Derivative. These coefficients are used by the algorithm to determine how to scale the output control value as the system gets closer or further away from its goal. We won&#x2019;t get into the details behind exactly how PID controllers work but getting more knowledge on the subject is highly recommended. I have found the <a href="https://www.youtube.com/watch?v=wkfEZmsQqiA" rel="noopener">series here</a> to be an excellent introduction.</p><p>The system uses 2 PID controllers to solve the 2D quadcopter control problem;</p><p><strong>Altitude controller</strong>&#x200A;&#x2014;&#x200A;Provides the drone with a thrust value required to drive the quadcopter to a desired high.</p><p><strong>Attitude controller</strong>&#x200A;&#x2014;&#x200A;Provides the drone with moment values to help stabilize the aircraft.</p><p>Here is what the control loop looks like. Note that only the desired elevation (y-axis) is provided as an overall system input. The controller&#x2019;s job is to drive the quadcopter to that altitude while ensuring a balanced orientation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*CMXOa6x3URQslPK2SXed_A.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>Diagram by&#xA0;Author</figcaption></figure><p><strong>Position Driver&#x200A;</strong>&#x2014;&#x200A;Represents the general control loop input&#x200A;&#x2014;&#x200A;<strong>altitude.</strong></p><p><strong>Error Estimator&#x200A;</strong>&#x2014;&#x200A;Component responsible for providing the PID controllers with the deviation between the desired state (<strong>altitude</strong> and <strong>neutral orientation</strong>) and actual values.</p><p><strong>PID Altitude Controller</strong>&#x200A;&#x2014;&#x200A;This PID controller uses the position error from the <strong>error estimator </strong>to derive a <strong>u1 </strong>value<strong> </strong>or the overall desired <strong>vertical thrust</strong>.</p><p><strong>PID Attitude Controller</strong>&#x200A;&#x2014;&#x200A;This PID controller uses the orientation error from the <strong>error estimator </strong>to derive a <strong>u2 </strong>value or the required <strong>torque</strong>.</p><p><strong>Motor Mixing Algorithm (MMA)</strong>&#x200A;&#x2014;&#x200A;This algorithm uses <strong>u1, u2 </strong>and combines them with the drone&#x2019;s <strong>current orientation</strong> to determine the required total thrust for each rotor.</p><p><strong>Plant / Drone</strong>&#x200A;&#x2014;&#x200A;The physical (or virtual) drone that interacts with the environment and provides feedback to the system with actual state values.</p><h3 id="the-code">The Code</h3><p>With all of that out of the way let&#x2019;s get to building the actual simulation. <a href="https://unity.com/" rel="noopener">Unity</a> is used due to its built-in physics engine. The <a href="https://github.com/adidinchuk/2d-unity-quadcopter-sim" rel="noopener">GitHub repository can be found here</a> containing a working instance of the simulation.</p><p>One might be able to get away without any Unity experience but getting at least some familiarity is highly recommended even if you don&#x2019;t have any game development aspirations. <a href="https://brackeys.com/" rel="noopener">Brackeys</a> has some fantastic resources such as this <a href="https://www.youtube.com/watch?v=on9nwbZngyw" rel="noopener">one</a> that can get you started.</p><h4 id="the-folder-structure">The folder structure</h4><p>The project was organized into the following file structure.</p><pre><code>&#x251C;&#x2500;&#x2500;&#x2500;Prefabs
&#x2502;   &#x251C;&#x2500;&#x2500;&#x2500;Quadcopter - prefab of the complete quadcopter
&#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;Thruster - prefab for a thruster object
&#x251C;&#x2500;&#x2500;&#x2500;Scenes
&#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;Main - the only scene in the project
&#x251C;&#x2500;&#x2500;&#x2500;Scripts
&#x2502;   &#x251C;&#x2500;&#x2500;&#x2500;PIDController - PID script obejcts
&#x2502;   &#x251C;&#x2500;&#x2500;&#x2500;Quadcopter - script for the quadcopter object
&#x2502;   &#x251C;&#x2500;&#x2500;&#x2500;FlightController - script to bring everything together
&#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;Thruster - script containing control for rotor objects</code></pre><h4 id="the-scene">The scene</h4><p>All scene and camera default values are kept and the scene is only updated with the following objects:</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*XqUHZntkaziFVyRI_OEo1A.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p><strong>Quadcopter</strong>&#x200A;&#x2014;&#x200A;The quadcopter, contains a flight controller, a rigid body frame, and two thrusters.</p><p><strong>Platform</strong>&#x200A;&#x2014;&#x200A;A static rigid body providing the quadcopter with a stable liftoff surface.</p><p><strong>Event System&#x200A;</strong>&#x2014;&#x200A;Default event system object which was disabled as it is not used.</p><p><strong>Main Camera&#x200A;</strong>&#x2014;&#x200A;Default camera object.</p><h4 id="the-code-1">The code</h4><p>The repository with all the code can be found <a href="https://github.com/adidinchuk/2d-unity-quadcopter-sim" rel="noopener"><strong>here</strong></a><strong>. </strong>The script objects are described at a high level with a few notable lines of code called out.</p><p><strong>Thruster.cs</strong></p><p>This script is associated with each rotor and provides the quadcopter with thrust. At every physics engine tick, the rotor script computes the force it should be producing using a <strong>thrust coefficient </strong>and the <strong>simulated blade speed</strong>.</p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/60758663c6b3e7cf03e317af7fd2cfca.js"></script><!--kg-card-end: html--><p>Rotor thrust can be updated by calling the <strong>setRevolutionTarget()</strong> function and passing the desired RPM value. The thruster then updates the RPM value continuously until the target is reached using the <strong>updateRevolutionRate()</strong> function.</p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/d7da117d8469729cb67a01283fb6266d.js"></script><!--kg-card-end: html--><p><strong>Quadcopter.cs</strong></p><p>This script represents the quadcopter object, it is mapped to the attached rotor objects and contains the <strong>Motor Mixer Algorithm </strong>code<strong>.</strong></p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/f15d91be5c387d40d950d9bae3773f62.js"></script><!--kg-card-end: html--><p>The <strong>u1 </strong>value is adjusted to account for gravity by multiplying by <strong>Cos(phi)</strong>, where <strong>phi</strong> is the drone&#x2019;s current <strong>angular orientation angle</strong>. The <strong>u2 </strong>value (moment input) is applied as a positive to the left rotor and a negative to the right to generate the required moment.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2022/02/image.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy" width="540" height="580"><figcaption>Diagram by Author</figcaption></figure><p><strong>Note that the values as passed directly as units of force to the rotors. This can be done because of the general flexibility of PID control systems. </strong>The control loop scales the magnitude of the inputs based on how the system reacts.</p><p><strong>PIDController.cs</strong></p><p>This is the main PID control script and the core logic is packed into the <strong>GetPIDOutput()</strong> function. This method is called every time a new value needs to be estimated. <strong>p</strong>, <strong>i</strong>, and <strong>d</strong> are the gain magnitudes, and <strong>kP</strong>, <strong>kI </strong>and <strong>kD </strong>are the coefficients that can be tweaked through the <strong>FlightController.cs </strong>script to change the PID controller&#x2019;s performance.</p><p><a href="https://www.youtube.com/watch?v=wkfEZmsQqiA" rel="noopener">Here</a> is a great resource to learn move about PID control logic.</p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/2539f62e7a5f8d2f1658744522d8d47f.js"></script><!--kg-card-end: html--><p>The integral term is updated using the model&#x2019;s previous integral value and is intended to provide the model with a type of &#x201C;memory&#x201D;. This is great at helping the system adapt to unknown variables but it can also get us into trouble. For example, if the drone is still some significant distance from the target destination but has reached its maximum velocity the integral term will continue to grow past the maximum. This will cause sub-optimal deacceleration as the system will have to undo the winded-up excess. A more detailed explanation can be found <a href="https://youtu.be/NVLXCwc8HzM?t=163" rel="noopener">here</a> and it discusses the concept of preventing this over-saturation by the clamping (or stopping accumulation) of the integral term if the PID output exceeds the system capability threshold.</p><p><strong>FlightController.cs</strong></p><p>This is the script that pulls everything together. The desired position and Thrust, and Roll PID coefficient can be set through this object.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*rTZcTlIIseMxesU3X_DSUw.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>At every physics engine update, this script computes the current error state, passes the error to the PID controllers, and feeds the resulting <strong>u1 </strong>and <strong>u2</strong> values through to the drone Motor Mixer Algorithm.</p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/24174952cb5104cdee257a35361c250b.js"></script><!--kg-card-end: html--><h3 id="the-simulation">The Simulation</h3><p>The code in the <a href="https://github.com/adidinchuk/2d-unity-quadcopter-sim" rel="noopener">repo</a> should run as-is out of the box but how the parameters of the simulation can be manipulated are covered below.</p><h4 id="thrusters">Thrusters</h4><p>The drone has left and right thrusters, these can be manipulated separately, however for best results their parameters should be identical. Each thruster also has a rigid body with a defined <strong>mass </strong>(0.3) and this rigid body is attached to the parent drone body using a <strong>2D Fixed Joint</strong>.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*9xIpEFJhUD_xvHlHsjfBVg.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>The main Thruster.cs parameters to configure would be the <strong>thrust coefficient</strong>, <strong>maximum RPM,</strong> and the <strong>spinup rate</strong>.</p><h4 id="quadcopter">Quadcopter</h4><p>The quadcopter also has a rigid body with a defined <strong>mass</strong> (1) along with a simple collider to prevent it from passing through the surface platform.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*oW0ooqAlULV2XLiezIKKQg.png" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><p>The rotors and rotor count are set in the <strong>Quadcopter.cs</strong> script and the target position and PID control coefficients in the <strong>FlightControl.cs</strong> script.</p><h4 id="running-the-code">Running the Code</h4><p>When the scene is run, the quadcopter lifts off the ground, moves towards the specified altitude, and settles there.</p><p><em>*The </em><strong><em>red lines</em></strong><em> are debug lines showing thrust.</em></p><p>The <strong>P</strong> thrust values can be modified to change how quickly the drone reaches the desired high.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*1KdD_3Bd491HiFm0docCoA.gif" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>proportional term = 25, integral term = 2.5, derivative term =&#xA0;4</figcaption></figure><p>The <strong>D</strong> thrust value dictates how smooth our trajectory is and can be used to minimize overshooting at a cost of slower convergence.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*vXg3tDCL6xiAZrF-p273_w.gif" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>proportional term = 25, integral term = 2.5, derivative term =&#xA0;11</figcaption></figure><p>The <strong>I</strong> thrust value allows the system to overcome unforeseen disturbances in the environment and have a smoother recovery. The drone uses the integral term value of 2.5 which allows it to account for the force of gravity that was omitted from the model altogether. Here is what it looks like with the integral term set to 0&#x200A;&#x2014;&#x200A;the drone fails to reach the target altitude of 10.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*R50RA8RYwKaHKQJ9IVoBkA.gif" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"><figcaption>proportional term = 25, integral term = 0, derivative term = 11</figcaption></figure><p>The roll PID coefficients provide the same role but for pitch. Here is what it looks like when the orientation and position of the drone are disturbed.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*V7BhEO7Mnhq16XsBemDNug.gif" class="kg-image" alt="2D Rotorcopter Mechanics and PID Control with Unity" loading="lazy"></figure><h3 id="conclusion">Conclusion</h3><p>If you got this far you should either have a working 2D quadcopter Unity simulation or at least the knowledge of how to create one. In future articles, I hope to increase the complexity of this simulation, including automated motion planning and the adaptation to 3 dimensions.</p><p>Please let me know if I have made any errors or omitted anything. Feel free to post any questions as comments as well.</p><p>Happy coding!</p>]]></content:encoded></item><item><title><![CDATA[Under the Hood with GCP’s App Engine]]></title><description><![CDATA[A deeper look at how to configure the App Engine and how to decipher the costs]]></description><link>http://www.theappliedarchitect.com/under-the-hood-with-gcps-app-engine/</link><guid isPermaLink="false">609dc3c83888470001d8b045</guid><category><![CDATA[GCP]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[hosting]]></category><category><![CDATA[app engine]]></category><category><![CDATA[big data]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Fri, 14 May 2021 00:36:30 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/05/hoover-tung-BslSDcQww0M-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<hr><img src="http://www.theappliedarchitect.com/content/images/2021/05/hoover-tung-BslSDcQww0M-unsplash.jpg" alt="Under the Hood with GCP&#x2019;s App Engine"><p>Google&#x2019;s App Engine service is a great way to get web applications up and running with minimal effort. In fact, I have an <a href="http://www.theappliedarchitect.com/deploy-a-node-js-application-with-app-engine-in-10-minutes/">article</a> on how you can spin up a Node.js application on this platform in about 10 minutes. This service is part of Google&#x2019;s <a href="https://cloud.google.com/free" rel="noopener">free tier</a> and getting started with the App Engine is easy but one can struggle with understanding the different configuration options and the pricing structure. This article outlines in three sections what you need to consider when configuring and deploying your applications on the App Engine platform.</p><p><strong>Hosting Options</strong>&#x200A;&#x2014;&#x200A;Things to keep in mind when configuring and deploying your App Engine application.</p><p><strong>Pricing</strong> &#x2014;How App Engine charges are incurred.</p><p><strong>Other Considerations&#x200A;</strong>&#x2014;&#x200A;Some other considerations when deploying an App Engine application.</p><p>Let&#x2019;s get started.</p><h2 id="hosting-options">Hosting Options</h2><p>When deploying an application you must provide instructions to the App Engine on what settings to use via a <code>app.yaml</code> config file. This section aims to help you understand the difference in deployment options and how they can be specified in this file.</p><p>Here is an example of a <code>app.yaml</code> file:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><h3 id="runtime">Runtime</h3><p>When deploying an application the runtime language has to be specified. Documentation on support runtime environments can be found <a href="https://cloud.google.com/appengine/docs/standard/runtimes" rel="noopener">here</a> and in the config file the <code>runtime</code> attribute is used&#x200A;&#x2014;&#x200A;here is an example for a deployment using Node.js v14:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><h3 id="environment">Environment</h3><p>You may select from two different environment types&#x200A;&#x2014;&#x200A;<strong>Standard </strong>and <strong>Flexible. </strong>This <a href="https://cloud.google.com/appengine/docs/the-appengine-environments" rel="noopener">document</a> outlines the detailed differences between the two options. In short, <strong>Flexible </strong>environments will cost you much more to run and are recommended only if you are using a language not supported by the <strong>Standard </strong>environment. Here is the list of supported languages:</p><ul><li>Python 2.7, Python 3.7, Python 3.8, Python 3.9 (preview)</li><li>Java 8, Java 11</li><li>Node.js 8, Node.js 10, Node.js 12, and Node.js 14 (preview)</li><li>PHP 5.5, PHP 7.2, PHP 7.3, and PHP 7.4</li><li>Ruby 2.5, Ruby 2.6, and Ruby 2.7</li><li>Go 1.11, Go 1.12, Go 1.13, Go 1.14, Go 1.15 (preview)</li></ul><p>There are other reasons to go Flexible but it should be an exception. You can find the full feature matrix <a href="https://cloud.google.com/appengine/docs/the-appengine-environments#comparing_high-level_features" rel="noopener">here</a> contrasting environment type capability. The environment type can be specified in the <code>app.yaml</code> file via the <code>env</code> attribute&#x200A;&#x2014;&#x200A;the values are either <strong>flex </strong>or <strong>standard</strong>:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><h3 id="instance-class">Instance Class</h3><p>Like most other cloud services, when deploying an App Engine application you may select a <a href="https://cloud.google.com/appengine/docs/standard#instance_classes" rel="noopener">class</a> that will dictate allocated resources and functionality options.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*tHpAcGg7uAp2y_hZi6bKBA.png" class="kg-image" alt="Under the Hood with GCP&#x2019;s App Engine" loading="lazy"><figcaption>Screen capture from GCP&#xA0;docs</figcaption></figure><p>It&#x2019;s important to remember that Google&#x2019;s free tier <a href="https://cloud.google.com/appengine/quotas#Instances" rel="noopener">covers</a> 28 hours of F1or 9 hours of B1 instance usage for free per day.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*ZVkfVC12F--Z8eC9ArVMeA.png" class="kg-image" alt="Under the Hood with GCP&#x2019;s App Engine" loading="lazy"><figcaption>Screen capture from GCP&#xA0;docs</figcaption></figure><p><strong>F instances </strong>are considered &#x201C;Front End Instances&#x201D; and <strong>B instances</strong> are considered &#x201C;Back End Instances&#x201D;. As you can see in the above table, the main difference is the scaling options available. The instance type can be specified in the <code>app.yaml</code> file via the <code>instance_class</code> attribute:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><h3 id="service">Service</h3><p>The App Engine runs applications as services. Your first App Engine deployment is associated with the <strong>default</strong> service but anything you deploy afterward must be associated with a dedicated service name. If a specified service does not exist, a new one will be created, and if one exists the pushed application will take its place. Failing to specify a service in your <code>app.yaml</code> file will have your default service application overwritten. You can see the list of running services in your App Engine dashboard.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*oo7Hv5q-iugGKH1v19Ih9Q.png" class="kg-image" alt="Under the Hood with GCP&#x2019;s App Engine" loading="lazy"><figcaption>Screen capture from GCP&#xA0;docs</figcaption></figure><p>The environment type can be set in the <code>app.yaml</code> file via the <code>service</code> attribute :</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><h3 id="scaling-types">Scaling Types</h3><p>Instance scaling is a very important configuration to understand because it can significantly impact your costs and performance. As mentioned, <strong>F-type</strong> instances support automatic scaling and <strong>B-type</strong> instances can leverage either manual or basic scaling. You can find the matrix of the options <a href="https://cloud.google.com/appengine/docs/standard/python/how-instances-are-managed#scaling_types" rel="noopener">here</a> and the configuration flag details can be found <a href="https://cloud.google.com/appengine/docs/standard/python/config/appref#scaling_elements" rel="noopener">here</a>. Here is a summary.</p><p><strong>Automatic Scaling&#x200A;</strong>&#x2014;&#x200A;Instances are created and terminated based on requests and performance. Configuration can get a little complicated but I would recommend at the very least setting the min and max setting for total and idle instances using the <code>max_instances</code>, <code>min_instances</code>, <code>min_idle_instances</code> and <code>max_idle_instances</code> attributes. This should prevent incurring unexpected charges. There are other flags like <code>target_cpu_utilization</code> and <code>max_pending_latency</code> that the App Engine can use to optimize performance and trigger scaling changes. Here is an example running 0 to 2 instances and a restriction of at most 1 idle instance:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
service: my-node-app
automatic_scaling:    
    max_instances: 2    
    min_instances: 0    
    min_idle_instances: 0    
    max_idle_instances: 1</code></pre><p><strong>Basic Scaling&#x200A;</strong>&#x2014;&#x200A; Instances are spun up and down as the load on the application fluctuates. In the <code>app.yaml</code> file the maximum number of instances can be set using the <code>max_instances</code> attribute and the idle period using the <code>idle_timeout</code> attribute like so:</p><pre><code>runtime: nodejs14
env: standard
instance_class: B1
service: my-node-app
basic_scaling:
  max_instances: 11
  idle_timeout: 10m</code></pre><p><strong>Manual Scaling</strong>&#x200A;&#x2014;&#x200A;Spins up and maintains a set number of instances regardless of the load on the service. In the <code>app.yaml</code> file the number of instances can be set using the <code>instances</code> attribute like so:</p><pre><code>runtime: nodejs14
env: standard
instance_class: B1
service: my-node-app
manual_scaling:
  instances: 5</code></pre><h2 id="costs-and-pricing">Costs and Pricing</h2><p>Google provides a set number of App Engine uptime hours in their free tier however, depending on your application is set up you may incur costs even if your application is up for a fraction of the allotted time. Understanding where these charges are coming from can sometimes be a challenge&#x200A;&#x2014;&#x200A;the detailed documentation on costs can be found <a href="https://cloud.google.com/appengine/pricing" rel="noopener">here</a> but I will outline the key elements below.</p><h3 id="instance-uptime-charges">Instance Uptime Charges</h3><p>As of today, Google will charge you for instance uptime as follows:</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*mg03an5WBJKkrHL2iCv9Ug.png" class="kg-image" alt="Under the Hood with GCP&#x2019;s App Engine" loading="lazy"></figure><p><strong>You might end up getting charged and reading the below three caveats might save you countless hours of scratching your head, and aimlessly searching for answers online.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*ldJCiDxuzAiK_9tBjNigqw.jpeg" class="kg-image" alt="Under the Hood with GCP&#x2019;s App Engine" loading="lazy"><figcaption>Photo by <a href="https://unsplash.com/@teckhonc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" data-href="https://unsplash.com/@teckhonc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" class="markup--anchor markup--figure-anchor" rel="noopener" target="_blank">T.H. Chia</a> on&#xA0;<a href="https://unsplash.com/s/photos/warning?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" data-href="https://unsplash.com/s/photos/warning?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" class="markup--anchor markup--figure-anchor" rel="noopener" target="_blank">Unsplash</a></figcaption></figure><p>As the documentation outlines <a href="https://cloud.google.com/appengine/quotas#Instances" rel="noopener">here</a>, you will be billed for uptime <strong>plus </strong>an additional 15 minutes when an instance spins down.</p><p><em>If your instance receives four evenly spaced out requests (every 15 minutes) within an hour and only takes </em><strong><em>a second</em></strong><em> to process each before spinning down you will still be charged </em><strong><em>60 minutes </em></strong><em>despite only using </em><strong><em>4 seconds of uptime</em></strong><em>.</em></p><p>Your app might scale to multiple instances if usage spikes and scaling is enabled. In this scenario, you may see an hourly charge that is a multiple of the above cost table.</p><p>Application versions might also produce some unexpected charges. By default, the App Engine maintains all of your application&#x2019;s version history and makes each version available mapped to a unique endpoint. If not used, these historic builds should only consume storage resources, but if requests are made against historic versions, they will spin up dedicated instances&#x200A;&#x2014;&#x200A;this will of course come with additional utilization costs.</p><h3 id="quotas">Quotas</h3><p>The App Engine caps your application&#x2019;s utilization of resources through <a href="https://cloud.google.com/appengine/quotas" rel="noopener">quotas</a>. There are three types of quotas; free, daily, and per-minute, if you are deploying hobby applications it is unlikely that you will come close to exceeding any of these quotas. If you do exceed a quota limit your application will be unavailable until the resources are reset at the end of the period, you can read more about this <a href="https://cloud.google.com/appengine/quotas#When_a_Resource_is_Depleted" rel="noopener">here</a>.</p><h2 id="other-considerations">Other Considerations</h2><h3 id="google-cloud-storage">Google Cloud Storage</h3><p>There are a few different ways for<strong> </strong>the App Engine to leverage GCS:</p><ol><li>The <code>staging.&lt;project-id&gt;.appspot.com</code> bucket is used to stage some objects as applications are deployed</li><li>The <code>us.artifacts.&lt;project-id&gt;.appspot.com</code> bucket is used to store build artifacts</li><li>Some applications may use buckets for runtime object storage</li><li>Buckets can be used to store your source build files for App Engine deployments</li></ol><p>GCS has a great free usage tier as outlined <a href="https://cloud.google.com/storage/pricing#cloud-storage-always-free" rel="noopener">here</a>.</p><h3 id="other-services">Other Services</h3><p>The App Engine might utilize a few other Google Cloud Platform services for general operation. The utilization of these services should be covered under the free tier but it&#x2019;s important to understand what these services are in case you do start seeing charges. You can see the full list <a href="https://cloud.google.com/storage/pricing" rel="noopener">here</a>.</p><h2 id="conclusion">Conclusion</h2><p>Although the App Engine is a great way to get your application up and running on a highly scalable platform, understanding how to properly configure your deployments and costs can be a bit confusing. In this article, I shine some light on a few areas where beginners typically get bogged down. I hope my post was informative and saves you some headache and googling.</p><p><strong>Good luck and happy coding!</strong></p><hr>]]></content:encoded></item><item><title><![CDATA[Setting up a GCP Pub/Sub Integration with Python]]></title><description><![CDATA[Stream data to and from GCP's Pub/Sub with Python.]]></description><link>http://www.theappliedarchitect.com/setting-up-gcp-pub-sub-integration-with-python/</link><guid isPermaLink="false">6085cb16bf0ce2000116ec91</guid><category><![CDATA[python]]></category><category><![CDATA[GCP]]></category><category><![CDATA[pub/sub]]></category><category><![CDATA[cloud]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[programming]]></category><category><![CDATA[big data]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Sun, 25 Apr 2021 20:23:03 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/04/david-clode-aM8NxYj-kjY-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2021/04/david-clode-aM8NxYj-kjY-unsplash.jpg" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python"><p><a href="https://en.wikipedia.org/wiki/Python_%28programming_language%29" rel="noopener">Python</a> is a popular language for all sorts of data processing today. Use cases range from web applications and machine learning applications all the way to hardware control on devices like the <a href="https://en.wikipedia.org/wiki/Raspberry_Pi" rel="noopener">RaspberryPi</a>. When it comes to these even systems and real-time data processing, leveraging Pub/Sub platforms can add modularity and scalability to your solutions&#x200A;&#x2014;&#x200A;<a href="https://towardsdatascience.com/dont-miss-out-on-pub-sub-5dbfa15cf3d0" rel="noopener">you can read more about this here</a>.</p><p>Read about why I used Google Cloud Platform tools for my hobby projects <a href="https://appliedarchitect.medium.com/google-cloud-platform-gcp-vs-amazon-web-services-aws-for-the-hobbyist-e79326a8177" rel="noopener">here</a>.</p><h3 id="objectives">Objectives</h3><p>In this article, I will walk through setting up a Python application to publish and consume data from <a href="https://cloud.google.com/pubsub" rel="noopener">Google&#x2019;s Pub/Sub</a>. </p><p><strong>Time required: </strong>15 minutes</p><h3 id="prerequisites">Prerequisites</h3><p>To follow along, you should have the following:</p><ol><li><a href="https://wiki.python.org/moin/BeginnersGuide" rel="noopener">A basic understanding of how Python works</a></li><li><a href="https://www.python.org/downloads/" rel="noopener">Python 3.x installed on your machine</a></li><li>A Google Cloud Platform <a href="https://cloud.google.com/apigee/docs/hybrid/v1.1/precog-gcpaccount" rel="noopener nofollow noopener">account</a> and a <a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener nofollow noopener">project</a></li></ol><h3 id="let%E2%80%99s-do-some-coding">Let&#x2019;s Do Some Coding!</h3><h4 id="gcp-%E2%80%94-service-account-setup">GCP&#x200A;&#x2014;&#x200A;Service Account Setup</h4><p>First things first, let&apos;s get all the configuration done in GCP. A GCP Service Account and private key are needed to access the Pub/Sub service from a Python application.</p><p>The full list of your service accounts can be accessed <a href="https://console.cloud.google.com/iam-admin/serviceaccounts" rel="noopener">here</a> and a new service account can be added using <a href="https://console.cloud.google.com/iam-admin/serviceaccounts/create" rel="noopener">this link</a>. Give your account a name and id &#x2014;&#x200A; both can be the same but the id must be unique&#x200A;&#x2014;&#x200A;I named mine <code>python-tester</code>.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*_zsAZYcyPi4larXRgcFyhg.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>Click <strong>create</strong> and add the <code>Pub/Sub Publisher</code> and <code>Pub/Sub Subscriber</code> roles to ensure that this account can both consume data from and publish data to your Pub/Sub topic(s).</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*jq9aAfnGPwme-t96F4mYYQ.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>From here you can click <strong>done</strong>.</p><p>Next, we need to generate a private key that our Python application will use when communicating with GCP. Find the service account you just created and select the <strong>Manage keys</strong> option.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*halvexjfQitMciVoEo5bpg.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>Use the <strong>Add Key</strong> button to add a new <strong>JSON </strong>key.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*6rPliSsoknE5aFO9uRzJPg.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>Clicking <strong>Create</strong> should download the private key file to your default Downloads directory. If you open the file you should see something like this:</p><pre><code>{  
  &quot;type&quot;: &quot;service_account&quot;,  
  &quot;project_id&quot;: &quot;...&quot;,  
  &quot;private_key_id&quot;: &quot;...&quot;,  
  &quot;private_key&quot;: &quot;-----BEGIN PRIVATE KEY-----...&quot;,  
  &quot;client_email&quot;: &quot;python-tester@...&quot;,  
  &quot;client_id&quot;: &quot;...&quot;,  
  &quot;auth_uri&quot;: &quot;https://accounts.google.com/o/oauth2/auth&quot;,  
  &quot;token_uri&quot;: &quot;https://oauth2.googleapis.com/token&quot;,
  &quot;auth_provider_x509_cert_url&quot;: &quot;...&quot;,  
  &quot;client_x509_cert_url&quot;: &quot;...&quot;
}</code></pre><p>Make sure you keep track of this file as our Python application will need it.</p><h4 id="gcp-%E2%80%94-pubsub-topic-setup">GCP&#x200A;&#x2014;&#x200A;Pub/Sub Topic Setup</h4><p>Before we can push/pull data from Pub/Sub we need to create a topic. You can see all your active topics <a href="https://console.cloud.google.com/cloudpubsub/topic/list" rel="noopener">here</a>. Create a new topic, give it a name and leave the default subscription option checked&#x200A;&#x2014;&#x200A;I named my topic <code>my-python-topic</code> .</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2021/05/image.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy" width="532" height="388"></figure><p>Make sure you leave the <strong>Add default subscription option</strong> checked and click <strong>Create Topic</strong> &#x2014;&#x200A; you should see the new topic appear in your topic list. Your default subscription will have the name of your topic with a <code>-sub</code> suffix, in my case it is named <code>my-python-topic-sub</code> .</p><h4 id="python-%E2%80%94-writing-the-producer-and-consumer">Python&#x200A;&#x2014;&#x200A;Writing the Producer and Consumer</h4><p>Before writing code, you must have <a href="https://www.python.org/downloads/" rel="noopener">Python 3.x installed</a> along with the<code>google-api-python-client</code> and <code>google-cloud-pubsub</code> GCP libraries. You can install these with pip/pip3 using the following:</p><pre><code>pip3 install --upgrade google-api-python-client
pip3 install --upgrade google-cloud-pubsub</code></pre><p>Somewhere on your machine create a folder for your Python code.</p><pre><code>mkdir pub-sub-test
cd pub-sub-test</code></pre><p>Move your private key generated in the <strong>GCP&#x200A;&#x2014;&#x200A;Service Account Setup </strong>section<strong> </strong>to this new folder. If you lost your key, you can generate a new one using the same instructions.</p><p>Create your main executable Python file in this directory&#x200A;&#x2014;&#x200A;I am calling mine <code>code.py</code> and add the following content:</p><!--kg-card-begin: html--><script src="https://gist.github.com/adidinchuk/54d5cdc36c749103eb9bb87f17334df7.js"></script><!--kg-card-end: html--><p>The GCP library expects an environment variable called <code>GOOGLE_APPLICATION_CREDENTIALS</code> to point to the private key. We set this value on <strong>line 2</strong> with:</p><pre><code>os.environ[&quot;GOOGLE_APPLICATION_CREDENTIALS&quot;]=&quot;YYYY.json&quot;</code></pre><p>Make sure to replace <code>YYYY.json</code> with the path/name to your private key file.</p><p>PUB_SUB_PROJECT on <strong>Line 12 </strong>should be updated with the id of your GCP project, you can find the id on the Pub/Sub <a href="https://console.cloud.google.com/cloudpubsub/topic/list" rel="noopener">list page</a>. It would be the value between <strong>projects </strong>and <strong>topics - </strong>projects/<strong>YYY</strong>/topics/my-python-topic.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*2KHvJA5MsLRFXax9ntHSJA.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>Your project should now look like this:</p><pre><code>&#x251C;&#x2500;&#x2500; pub-sub-test
&#x2502; &#x251C;&#x2500;&#x2500; code.py
&#x2502; &#x251C;&#x2500;&#x2500; YYYY.json</code></pre><p>I tried my best to have the code be as self-explanatory as possible but essentially:</p><p><strong>process_payload: </strong>A callback function that handles events consumed from Pub/Sub, any logic you want to apply to the payload should be added here.</p><p><strong>push_payload: </strong>Takes a payload (JSON) and pushes it to the provided a Pub/Sub topic/project id combination.</p><p><strong>consume_payload:</strong> Checks the provided subscription/project combination for new events and if data exists the callback function will be called for processing. The timeout period serves as an interrupt.</p><p>The rest of the code continuously pushes and consumes data until the program is terminated. You can run the code with <code>python3 code.py</code> and should see something like this in the terminal:</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*4QtvyXruniEsRfEGQ9tOlg.png" class="kg-image" alt="Setting up a GCP Pub/Sub Integration with&#xA0;Python" loading="lazy"></figure><p>There you have it, a bare-bones implementation but this should be enough to get you jump-started with leveraging GCP&#x2019;s Pub/Sub with Python.</p><h3 id="conclusion">Conclusion</h3><p>Google&#x2019;s Pub/Sub platform is great for handling large amounts of data and decoupling the various components of your architecture. In this article, I walked you through how Pub/Sub can be leveraged with Python applications. I hope that you were able to learn something from this post.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[Deploy a Node.js Application With App Engine in 10 Minutes!]]></title><description><![CDATA[A walkthrough of deploying a simple node.js application on Google Cloud Platform's App Engine.]]></description><link>http://www.theappliedarchitect.com/deploy-a-node-js-application-with-app-engine-in-10-minutes/</link><guid isPermaLink="false">607307410adf740001a8e51e</guid><category><![CDATA[GCP]]></category><category><![CDATA[node.js]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[hosting]]></category><category><![CDATA[app engine]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Sun, 11 Apr 2021 14:39:10 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/04/emile-perron-xrVDYZRGdw4-unsplash--1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2021/04/emile-perron-xrVDYZRGdw4-unsplash--1-.jpg" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!"><p>Google Cloud&#x2019;s <a href="https://cloud.google.com/appengine" rel="noopener">App Engine</a> allows you to deploy scalable web applications on a platform fully managed by Google. These applications can range from back-end services and API layers to front-end applications running on Angular and React frameworks. Google provides 28 daily hours of run time with this service for free so you can get away with some free hosting!</p><p>In this article, I will walk you through the deployment of a simple Node.js application on this App Engine platform.</p><h3 id="prerequisites"><strong>Prerequisites</strong></h3><p>To follow along, you should have the following:</p><ol><li><a href="https://www.google.com/search?q=node.js+getting+started&amp;rlz=1C1CHBF_enCA882CA882&amp;oq=node.js+getting+started&amp;aqs=chrome.0.69i59j0i22i30l9.3739j0j4&amp;sourceid=chrome&amp;ie=UTF-8" rel="noopener">A basic understanding of how Node.js works</a></li><li><a href="https://nodejs.org/en/download/" rel="noopener">Node installed on your local machine</a></li><li>A Google Cloud Platform <a href="https://cloud.google.com/apigee/docs/hybrid/v1.1/precog-gcpaccount" rel="noopener">account</a> and a <a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener">project</a></li></ol><h3 id="let%E2%80%99s-build-and-deploy-our-application">Let&#x2019;s Build and Deploy our Application!</h3><h4 id="create-the-project">Create the Project</h4><p>Somewhere on your local machine initialize a new Node.js project.</p><pre><code>mkdir helloworld
cd helloworld
npm init</code></pre><p>Create a simple execution file <code>index.js</code> with the following content in the <code>helloworld</code> folder:</p><pre><code>const express = require(&apos;express&apos;);
const app = express();

app.get(&apos;/&apos;, (req, res) =&gt; {
  res.send(&apos;GCP App Engine!&apos;);
});

const PORT = process.env.PORT || 8080;

app.listen(PORT, () =&gt; {
  console.log(`Server listening on port ${PORT}...`);
});</code></pre><p>Our application won&#x2019;t do much and just returns a <code>&#x2018;GCP App Engine!&#x2019;</code> string when called.</p><p>Add the following start script and express dependency to the created <code>package.json</code> file:</p><pre><code>{
 ...
 &quot;scripts&quot;: {
   &quot;start&quot;: &quot;node index.js&quot;
 },
 &quot;dependencies&quot;: {
   &quot;express&quot;: &quot;^4.16.3&quot;
 }
}</code></pre><p>Express is not required and I am using it to make deployment easier. The start script will be used by the App Engine to launch your application. Note that <code>index.js</code> matches the name of my main execution file created above.</p><p>Install dependencies (express) and make sure your app runs locally:</p><pre><code>npm install
node index.js</code></pre><p>Navigate to <a href="http://localhost:8080/" rel="nofollow">http://localhost:8080</a> and you should see:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*3xeertPfrfSmqu3lYW93Lw.png" class="kg-image" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>The application is ready to go but we need to provide some information to the App Engine so that it knows how to deploy our code. We do this using a <a href="https://en.wikipedia.org/wiki/YAML#:~:text=YAML%20%28a%20recursive%20acronym%20for,is%20being%20stored%20or%20transmitted." rel="noopener">YAML </a>file. The configuration in this file can get pretty complicated and there are a lot of options to configure but in our case, I will keep it simple. Create a file called <code>app.yaml</code> &#xA0;in the <code>helloworld</code> folder and add the following content:</p><pre><code>runtime: nodejs14
env: standard
instance_class: F1
automatic_scaling:
  min_idle_instances: automatic
  max_idle_instances: automatic
  min_pending_latency: automatic
  max_pending_latency: automatic</code></pre><p>My Node.js runtime is version is 14.16.0, feel free to change the runtime if your version is different. &#xA0;We will be using a<a href="https://cloud.google.com/appengine/docs/standard#instance_classes" rel="noopener"> standard environment and an F1 instance</a> as these are covered by GCP&apos;s <a href="https://cloud.google.com/free">free quota</a>. </p><p>At this point, you should have the below file structure and we are ready to start migrating our code to GCP.</p><pre><code>&#x251C;&#x2500;&#x2500; helloworld
&#x2502; &#x251C;&#x2500;&#x2500; index.js
&#x2502; &#x251C;&#x2500;&#x2500; package.json
&#x2502; &#x251C;&#x2500;&#x2500; app.yaml</code></pre><h4 id="migrate-your-code">Migrate your Code</h4><p>Before deploying the application you will need to make the code available to GCP. You can do this by leveraging <a href="https://cloud.google.com/sdk" rel="noopener">Cloud SDK</a> on your local machine or by using the <a href="https://cloud.google.com/shell" rel="noopener">Cloud Shell</a> as I will be doing in this walk-through.</p><p>I will be using a <a href="https://cloud.google.com/storage">Cloud Storage</a> bucket to stage my code before pushing it to the App Engine. <a href="https://console.cloud.google.com/storage/create-bucket" rel="noopener">Create a new bucket to house your source code</a> (you can reuse an existing bucket if desired). For the region you should set Regional and the rest of the settings can be left default. In my case, I am creating a bucket called <code>sample-code-repo</code> and uploading the entire helloworld folder to the root.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*uxnP69fKARbP88JOKYPEug.png" class="kg-image" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>Next, we need to get the code uploaded to the <a href="https://cloud.google.com/shell" rel="noopener">Cloud Shell VM</a>, you can do this by opening the Cloud Shell terminal from any GCP console page or just click <a href="https://shell.cloud.google.com/" rel="noopener">here</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*SrF69jLGz1h6COFVyNfnWw.png" class="kg-image" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>To create the required folder structure on the Cloud Shell VM and sync the code from the bucket run the following commands, replacing <code>sample-code-repo/helloworld</code> with <code>&lt;source-code-bucket-name&gt;/&lt;app-folder-name&gt;</code> :</p><pre><code>mkdir helloworld
cd helloworld
gsutil rsync -r gs://sample-code-repo/helloworld .</code></pre><p>You will be asked to authorize the bucket access (pop-up) and once done running the <code>ls</code> command should confirm data replication:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*Lch3CUsxprH2sbjvMXM85Q.png" class="kg-image" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!" loading="lazy"><figcaption>Diagram by&#xA0;author</figcaption></figure><p>If you have another preferred method to migrate code to Cloud Shell (e.g. git), feel free to use it. At this point, our project is ready for deployment.</p><h4 id="deploy-the-app">Deploy the App</h4><p>To deploy the application on the App Engine we need to head back to our <a href="https://cloud.google.com/shell" rel="noopener">Cloud Shell VM</a> and run the following:</p><pre><code>cd helloworld
gcloud app deploy</code></pre><p>For your first App Engine deployment you will need to specify a region - I used <code>us-central</code> for mine. &#xA0;The code will take a minute to compile and once done running <code>gcloud app browse</code> will output a link that you can use to access your now deployed application!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*EpwHBf-ROP4_4yg2AAxkBA.png" class="kg-image" alt="Deploy a Node.js Application With App Engine in 10&#xA0;Minutes!" loading="lazy"><figcaption>Diagram by author</figcaption></figure><h3 id="conclusion">Conclusion</h3><p>Google&#x2019;s App Engine is a great platform for rapidly deploying applications online at no cost. In this article, I walked you through how this platform can be used for Node.js applications but the same result can be accomplished with Java, Python, PHP, and Go. I hope that you were able to learn something from this post.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[Setup a Free Self-hosted Blog in Under 15 Minutes!]]></title><description><![CDATA[A walkthrough of deploying your very own Ghost blog using Google Cloud Platform’s Compute Engine for free.]]></description><link>http://www.theappliedarchitect.com/setup-a-free-self-hosted-blog-in-under-15-minutes/</link><guid isPermaLink="false">604d120292f9170001fd294b</guid><category><![CDATA[ghost]]></category><category><![CDATA[blog]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[GCP]]></category><category><![CDATA[hosting]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Sat, 13 Mar 2021 19:35:54 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/03/intricate-explorer-IPuEtxMny_c-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<h4></h4><img src="http://www.theappliedarchitect.com/content/images/2021/03/intricate-explorer-IPuEtxMny_c-unsplash.jpg" alt="Setup a Free Self-hosted Blog in Under 15&#xA0;Minutes!"><p>Ghost is a popular open-source blogging platform of which I am a huge advocate. <a href="https://theappliedarchitect.com/" rel="noopener">Here</a> is what a blog hosted on Ghost looks like. The platform is secure, lightweight, and very easy to use and customize. The Ghost team has a post on how their platform compares to WordPress <a href="https://ghost.org/vs/wordpress/" rel="noopener">here</a>.</p><p>There is a managed hosting fee if you want Ghost to host the blog for you but in this article, I will walk you through setting up a Dockerized ghost blog on Google Cloud Platform (GCP). With this approach, your blog can be hosted absolutely free and the setup should take under 15 minutes. You can read up on Ghost <a href="https://ghost.org/docs/" rel="noopener">here</a> and why I use GCP as my cloud platform <a href="https://appliedarchitect.medium.com/google-cloud-platform-gcp-vs-amazon-web-services-aws-for-the-hobbyist-e79326a8177" rel="noopener">here</a>.</p><p>Having a GCP account is a prerequisite and you can create it <a href="https://cloud.google.com/" rel="noopener">here</a> for free. If you are using AWS, Azure, or another cloud vendor you can follow the same steps for everything except the compute instance setup.</p><p>Let&#x2019;s get started!</p><h2 id="setup-the-compute-engine-instance">Setup the Compute Engine Instance</h2><p>Google&#x2019;s <a href="https://cloud.google.com/compute" rel="noopener">Compute Engine</a> is a service for deploying private Virtual Machines in the cloud. The GCP <a href="https://cloud.google.com/free" rel="noopener">always free tier</a> provides one of these VMs <strong>free </strong>for you to use and we will use this service to host our Ghost blog.</p><p>If you don&#x2019;t yet have a project in GCP or if you want to create a new project to be associated with your Ghost blog you can do that <a href="https://console.cloud.google.com/projectcreate" rel="noopener">here</a>.</p><p>The Compute Engine creation process can be started <a href="https://console.cloud.google.com/compute/instancesAdd" rel="noopener">here</a> or by clicking on the <strong>create instance</strong> button on the <a href="https://console.cloud.google.com/compute/instances" rel="noopener">VM instances</a> view.</p><ol><li>Specify a name for your instance (I will be using ghost-blog)</li><li>Under Machine configuration leaving the machine family as <strong>General Purpose</strong>, select <strong>N1 </strong>for series and <strong>f1-micro</strong> for Machine Type (free tier)</li><li>Change the Boot disk to the public image <strong>Ubuntu 20.10</strong> or <strong>Ubuntu 20.10 Minimal </strong>with boot disk type set to <strong><strong>Standard persistent disk</strong></strong></li><li>Ensure <strong>Allow HTTP traffic</strong> and <strong>Allow HTTPS traffic</strong> checkboxes are checked</li><li>Note the message in the top right indicating that the first <strong>744 hours of f1-micro instance usage are free this month&#x200A;</strong>&#x2014;&#x200A;if you don&#x2019;t see this message you might have selected the wrong machine type or the wrong region (must be us-west1, us-central1, us-east1)</li></ol><p>Here is what your configuration should look like:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2021/04/image-2.png" class="kg-image" alt="Setup a Free Self-hosted Blog in Under 15&#xA0;Minutes!" loading="lazy" width="1314" height="1118" srcset="http://www.theappliedarchitect.com/content/images/size/w600/2021/04/image-2.png 600w, http://www.theappliedarchitect.com/content/images/size/w1000/2021/04/image-2.png 1000w, http://www.theappliedarchitect.com/content/images/2021/04/image-2.png 1314w" sizes="(min-width: 720px) 720px"></figure><p>Click <strong>Create </strong>and wait for the instance to spin up. You should be automatically redirected to your VM instance list and once you see a green checkmark appears next to your instance it is ready to go.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*8ZMBpz-IgidwXKTOPnBPAw.png" class="kg-image" alt="Setup a Free Self-hosted Blog in Under 15&#xA0;Minutes!" loading="lazy"></figure><p>Note the external IP as we will need to further in this tutorial when we configure Ghost.</p><p>Click the <strong>SSH </strong>button under Connect to launch a session and get access to your new Compute Engine instance.</p><h2 id="get-your-instance-ready-for-ghost">Get Your Instance Ready for Ghost</h2><p>As mentioned above, I will be using Ubuntu 20.10 in this walk-through. If you are using a different OS, you will have to adjust your commands.</p><h3 id="update-the-package-list-install-your-favourite-text-editor">Update the Package List &amp; Install Your Favourite Text Editor</h3><p>I prefer <a href="https://www.nano-editor.org/" rel="noopener">nano</a> but you can use whatever suits you. Update the package list and install your editor.</p><pre><code>sudo apt-get update
sudo apt-get install nano</code></pre><h3 id="add-swap-space">Add Swap Space</h3><p>Small compute instances like the f1-micro come with limited memory (600MB in our case), which can slow things down and overload the CPU. If you are using a larger compute instance with sufficient memory you can skip this step, but I will start by adding <a href="https://www.enterprisestorageforum.com/hardware/what-is-memory-swapping/#:~:text=Memory%20swapping%20is%20a%20computer,random%20access%20memory%20%28RAM%29." rel="noopener">swap storage</a> to enhance performance. You want to add 2x of your available RAM in swap space&#x200A;&#x2014;&#x200A;in my case, this is 1.2GB of swap for 600MB of RAM. Create your swap file:</p><pre><code>sudo fallocate -l 1.2G /swapfile</code></pre><p>Change permissions to only allow root access:</p><pre><code>sudo chmod 600 /swapfile</code></pre><p>Set up a Linux swap area on the file:</p><pre><code>sudo mkswap /swapfile</code></pre><p>Make sure the changes are permanent by modifying the filesystem table:</p><pre><code>sudo nano /etc/fstab</code></pre><p>Append <code>/swapfile swap swap defaults 0 0</code> to the end of the file. It should look like this:</p><pre><code>LABEL=cloudimg-rootfs / ext4 defaults 0 1
LABEL=UEFI /boot/efi vfat defaults 0 1
/swapfile swap swap defaults 0 0</code></pre><p>*If you have never used nano before, you can use <strong>ctrl+x</strong> to close out of the file - enter <strong>y+enter </strong>when asked if you would like to save modified buffer.</p><p>Your instance now has an additional 1.2GB of swap memory.</p><h2 id="install-docker">Install Docker</h2><p>We will be using the Ghost Docker image to deploy our blog. Docker has an install walkthrough that you can follow <a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener">here</a> but I will include the relevant instructions in this section.</p><p>Update your package list and run an upgrade to make sure everything is up to date:</p><pre><code>sudo apt-get update
sudo apt-get upgrade</code></pre><p>Install required packages:</p><pre><code>sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release \
    software-properties-common</code></pre><p>Add Docker&#x2019;s official GPG key:</p><pre><code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</code></pre><p>Setup the stable repository:</p><pre><code>sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu groovy stable&quot;</code></pre><p>Run the update again and install docker:</p><pre><code>sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io</code></pre><p>You can check to ensure Docker is installed with the <code>sudo docker --version </code>command and see something like this in the output <code>Docker version 20.10.5, build 55c4c88</code> .</p><p>You are now ready to pull and configure the Ghost image!</p><h2 id="configure-and-deploy-ghost">Configure and Deploy Ghost</h2><p>There are a few different ways of deploying Ghost but as mentioned we will use Docker. There is a Ghost Docker image maintained on the Docker hub which makes things very simple for us.</p><p>Pull the latest Ghost docker image:</p><pre><code>sudo docker pull ghost:latest</code></pre><p>Create a directory to house your content and Docker config file and create said config file <strong>(make sure to change your IP/domain)</strong>:</p><pre><code>mkdir ghost_blog

echo &apos;{
  &quot;url&quot;: &quot;http://yourdomain-or-ip&quot;,
  &quot;server&quot;: {
    &quot;port&quot;: 2368,
    &quot;host&quot;: &quot;0.0.0.0&quot;
  },
  &quot;database&quot;: {
    &quot;client&quot;: &quot;sqlite3&quot;,
    &quot;connection&quot;: {
      &quot;filename&quot;: &quot;/var/lib/ghost/content/data/ghost.db&quot;
    }
  },
  &quot;mail&quot;: {
    &quot;transport&quot;: &quot;Direct&quot;
  },
  &quot;logging&quot;: {
    &quot;transports&quot;: [
      &quot;file&quot;,
      &quot;stdout&quot;
    ]
  },
  &quot;process&quot;: &quot;systemd&quot;,
  &quot;paths&quot;: {
    &quot;contentPath&quot;: &quot;/var/lib/ghost/content&quot;
  }
}&apos; &gt;&gt; ghost_blog/config.json</code></pre><p><em>*Replace </em><strong>http://yourdomain-or-ip </strong><em>with the external IP we noted in the </em><strong><em>Setup the Compute Engine Instance </em></strong><em>section. If you have a domain pointing to the IP you may use the domain instead.</em></p><p>Create a shell script file for quickly spinning up your Docker container <strong>(make sure to change your IP/domain)</strong>:</p><pre><code>echo &apos;# Set path variables
DATA_DIR=&quot;$PWD/ghost_blog&quot;
CONTAINER_NAME=&quot;ghost_blog&quot;

# Purge the existing container if running.
docker kill $CONTAINER_NAME
docker rm $CONTAINER_NAME

# Mount the volumes - content directory and config file
# and add the url variable for docker map as the public URL
docker run \
-d \
--restart=always \
-p 80:2368 \
-e url=&quot;http://yourdomain-or-ip&quot; \
-v $DATA_DIR/content:/var/lib/ghost/content \
-v $DATA_DIR/config.json:/var/lib/ghost/config.production.json \
--name $CONTAINER_NAME \
ghost&apos; &gt;&gt; run.sh</code></pre><p><em>*Replace </em><strong>http://yourdomain-or-ip </strong><em>with the external IP we noted in the </em><strong><em>Setup the Compute Engine Instance </em></strong><em>section. If you have a domain pointing to the IP you may use the domain instead.</em></p><p>You should not have the following folder structure:</p><pre><code>&#x251C;&#x2500;&#x2500; run.sh
&#x251C;&#x2500;&#x2500; ghost_blog
&#x2502; &#x251C;&#x2500;&#x2500; config.json</code></pre><p>To spin up your Docker container containing your Ghost image run:</p><pre><code>sudo sh ./run.sh</code></pre><p>You will see a cannot kill container error the first time you run the script. This is expected and will not cause any issues. Here is a sample output:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2021/03/image.png" class="kg-image" alt="Setup a Free Self-hosted Blog in Under 15&#xA0;Minutes!" loading="lazy" width="901" height="78" srcset="http://www.theappliedarchitect.com/content/images/size/w600/2021/03/image.png 600w, http://www.theappliedarchitect.com/content/images/2021/03/image.png 901w" sizes="(min-width: 720px) 720px"></figure><p>Navigate to your external IP and you should now see your blog instance (make sure you are using HTTP and not HTTPS).</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*zjNa9ugFv40Ij7l_6bjBWA.png" class="kg-image" alt="Setup a Free Self-hosted Blog in Under 15&#xA0;Minutes!" loading="lazy"></figure><p>To finish configuring your blog you will need to navigate to http://yourdomain-or-ip/ghost and follow the instructions to create your admin account.</p><p>Congratulations, you now have your very own Ghost blog page up and running!</p><h2 id="other-helpful-topics">Other Helpful Topics</h2><h3 id="updating-ghost">Updating Ghost</h3><p>With this setup, you can update your Ghost image by simply running <code>sudo docker pull ghost:latest</code></p><h3 id="setting-up-https-for-your-blog">Setting Up HTTPS for Your Blog</h3><p>If you would like to take your blog to the next level you need to set up a proper domain name and SSL certificate(s). I have an <a href="https://theappliedarchitect.com/setting-up-https-for-your-blog-certbot/">article</a> on doing this with NGINX and Certbot.</p><h3 id="persist-content-storage">Persist Content Storage</h3><p>With our setup, all of your content, configuration, and posts will live within the <code>ghost_blog</code> folder. To create a proper backup for your blog I recommend using git. You can initialize the <code>ghost_blog</code> folder as a git repository which allows for easy backups and restores.</p><h3 id="ip-and-domain-management">IP and Domain Management</h3><p>If your entry point to the blog changes to a new IP or a proper domain name you will need to update both the <code>ghost_blog/config.json</code> and <code>run.sh</code> files with the new IP/domain name. Once dun simply re-run the run.sh batch script.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[What The Heck Are These Cloud Storage Buckets?!]]></title><description><![CDATA[Understanding auto-generated GCS buckets, charge origins, and how to remove them.]]></description><link>http://www.theappliedarchitect.com/what-the-heck-are-these-cloud-storage-buckets/</link><guid isPermaLink="false">603431ab1d2c9700015ebad7</guid><category><![CDATA[GCP]]></category><category><![CDATA[Cloud Storage]]></category><category><![CDATA[GCS]]></category><category><![CDATA[google cloud platform]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Tue, 02 Mar 2021 03:54:28 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/03/pedro-da-silva-Y8AqKSRYUHQ-unsplash--1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2021/03/pedro-da-silva-Y8AqKSRYUHQ-unsplash--1-.jpg" alt="What The Heck Are These Cloud Storage Buckets?!"><p>I enjoy using the Google Cloud Platform (GCP) for hobby projects (check out why I use GCP <a href="https://appliedarchitect.medium.com/google-cloud-platform-gcp-vs-amazon-web-services-aws-for-the-hobbyist-e79326a8177" rel="noopener">here</a>) and <a href="https://cloud.google.com/storage" rel="noopener">Google&#x2019;s Cloud Storage</a> (GCS) product has made its way into my design several times. However, I quickly realized that other GCP services leverage GCS, creating buckets and filling them with objects.</p><p>At first, my use of GCS was light, and these system buckets didn&#x2019;t bother me, but then I started seeing charges on my bill (albeit just a few cents), and decided it was time to understand what these buckets were for and how I could remove or reduce the charges.</p><p>Understanding these charges can be a challenge and what creates the system buckets is loosely documented. I have documented my experience here hoping that it will help others avoid similar frustrations.</p><p>Below includes my investigation processes, discoveries, and the steps I took to minimize my GCS charges. If you are just interested in the solution you can skip to <strong>The Solution </strong>section.</p><p><strong>WARNING: Do not alter any buckets auto-generated by GCP (or their contents) without understanding their purpose. Some are tied to active processes and altering them can cause irreversible object corruption!</strong></p><h2 id="the-investigation">The Investigation</h2><p>All this started when I began incurring GCS costs, and if you are here investigating your charges you know that there isn&#x2019;t much detail in the billing dashboard. Here is what I see for February of 2021.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/0*Ye8GPvEjRPf6qoAG.png" class="kg-image" alt="What The Heck Are These Cloud Storage Buckets?!" loading="lazy"></figure><p>There is a charge of <strong>5 cents</strong> for <strong>1.45 GB month </strong>of storage under the <strong>US Multi-region</strong> SKU. I see two issues here:</p><ol><li>The first 5GB of storage should be free (thank you free tier!)</li><li>My usage of Cloud Storage should not come close to 1.45 GB</li></ol><p>The Google Cloud Storage <a href="https://cloud.google.com/storage/pricing" rel="noopener">pricing page</a> addresses my first issue. The free tier <strong><em>only </em></strong>applies to certain regions.</p><blockquote>Cloud Storage Always Free quotas apply to usage in <code>US-WEST1</code>, <code>US-CENTRAL1</code>, and <code>US-EAST1</code> <a href="https://cloud.google.com/storage/docs/bucket-locations#location-r" rel="noopener">regions</a>. Usage is aggregated across these 3 regions. Always Free is subject to change. Please see our <a href="https://cloud.google.com/free/docs/frequently-asked-questions" rel="noopener">FAQ</a> for eligibility requirements and other restrictions.</blockquote><p>Maybe I selected the wrong storage type for my buckets?</p><h4 id="cloud-storage-browser">Cloud Storage Browser</h4><p>Bucket details can be found on the Cloud Storage <a href="https://console.cloud.google.com/storage/browser" rel="noopener">browser</a> page:</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*ESYAAEuy2ayPDMQ4kkXzPA.png" class="kg-image" alt="What The Heck Are These Cloud Storage Buckets?!" loading="lazy"></figure><p>In this case, I have 6 buckets in total. Highlighted in green is an active bucket used to house some IoT device data. I did not directly create the others and some have the location type of Multi-region.</p><p>That partially explains the charges, but it&#x2019;s not clear what process created these buckets and what GCP uses them for. On that note&#x2026;</p><p><strong>WARNING: Do not alter any buckets auto-generated by GCP (or their contents) without understanding their purpose. Some are tied to active processes and altering them can cause irreversible object corruption!*</strong></p><p>*Repeated intentionally due to its importance!</p><p>The next step is to analyze bucket space utilization and understand where the costs are originating.</p><h4 id="the-monitoring-page">The Monitoring Page</h4><p>Although the GCS browser does not show total space utilization by bucket there are a few different ways of getting this information. I prefer the GCP <a href="https://console.cloud.google.com/monitoring" rel="noopener">monitoring</a> page. Here are Google&#x2019;s setup instructions when using the monitoring page for the first time:</p><blockquote><em>If you have never used Cloud Monitoring, then on your first access of </em><strong><em>Monitoring</em></strong><em> in the Google Cloud Console, a Workspace is automatically created and your project is associated with that Workspace. Otherwise, if your project isn&#x2019;t associated with a Workspace, then a dialog appears and you can either create a Workspace or add your project to an existing Workspace. We recommend that you create a Workspace. After you make your selection, click </em><strong><em>Add</em></strong><em>.</em></blockquote><p>Once loaded, the easiest way to get an overview of GCS usage is to select it under the resource dashboard.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*3yA83fwDO4getigZePgWDQ.png" class="kg-image" alt="What The Heck Are These Cloud Storage Buckets?!" loading="lazy"></figure><p>Expanding the legend of the Object Size graph on the resource dashboard provides a list of all buckets along with their current space utilization.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*JVHjO4oO-15uCys0sItH6Q.png" class="kg-image" alt="What The Heck Are These Cloud Storage Buckets?!" loading="lazy"></figure><p>In this case, the <strong>us.artifacts</strong> bucket is responsible for 99.7% of my total storage. The main cost driver has been identified!</p><h2 id="the-solution">The Solution</h2><p>Of the 5 auto-generated buckets in my Google Cloud Storage, 4 are multi-region and are incurring costs. I will outline what GCP processes are using each bucket for and how to minimize or eliminate the costs.</p><figure class="kg-card kg-image-card"><img src="https://cdn-images-1.medium.com/max/800/1*waK3F2zMzn4h7yA5UrtPHA.png" class="kg-image" alt="What The Heck Are These Cloud Storage Buckets?!" loading="lazy"></figure><h4 id="the-cloud-run-buckets">The Cloud Run Buckets</h4><p>The <code>&lt;project-id&gt;_cloudbuild</code> and <code>artifacts.&lt;project-id&gt;.appspot.com</code> &#xA0;buckets are utilized by the <a href="https://cloud.google.com/run" rel="noopener">Google Cloud Run</a> engine. When code is submitted to Cloud Run, the engine uses the <em>cloudbuild </em>bucket to stage build objects and the <em>artifacts </em>bucket as the <a href="https://cloud.google.com/build/docs/building/store-build-artifacts" rel="noopener">artifact</a> registry. It&#x2019;s not critical for you to know exactly what these objects are, but you should know that they are typically not critical after deployments and there is no reason for them to be in a Multi-region bucket.</p><p>The good news is that GCP allows to overload defaults with the <code>gcloud builds submit</code> <a href="https://cloud.google.com/sdk/gcloud/reference/builds/submit#--gcs-source-staging-dir" rel="noopener">command</a>. Here are the steps to ensure you incur no more GCS costs from your Clour Run deployments:</p><ol><li>Create a new bucket with your desired <strong>regional storage</strong> (eg. gcr_store)</li><li>Create a default folder for the build objects in this bucket (eg. source)</li><li>Create a default folder for the artifact objects in this bucket (eg. artifacts)</li><li>Create a <code>cloudbuild.yaml</code> file in your deployment directory with something like the following (note the location mapping to the new artifacts folder and the <code>gcr.io/cloud-builders/docker</code> indicating what <a href="https://cloud.google.com/build/docs/configuring-builds/create-basic-configuration" rel="noopener">builder</a> to use)</li></ol><pre><code>steps:
- name: &apos;gcr.io/cloud-builders/docker&apos;  
artifacts:
  objects:
    location: &apos;gs://gcr_store/artifacts&apos;
    paths: [&apos;*&apos;]</code></pre><p>5. Use the&#x200A; <code>--gcs-source-staging-dir</code> flag to specify where build objects should be saved when building new Cloud Run applications and include your config yaml file</p><pre><code>gcloud builds submit --gcs-source-staging-dir=gs://gcr_store/source --config cloudbuild.yaml</code></pre><p>6. Delete your auto-generated <code>&lt;project-id&gt;_cloudbuild</code> and <code>artifacts.&lt;project-id&gt;.appspot.com</code> buckets</p><p>5. (Optional) Add a lifecycle rule on your new bucket to delete objects older than X days (eg. 7 days)</p><p>Once done you should no longer have a Multi-region bucket associated with your Cloud Run deployment process and if you ever find the size of your custom bucket is getting out of hand you can implement Step 6.</p><h4 id="the-cloud-functions-bucket">The Cloud Functions Bucket</h4><p>The <code>gcf-sources-&lt;id&gt;-&lt;region&gt;</code> &#xA0; bucket is used for the storage of Google Cloud Function (GCF) objects and metadata. This folder is deployed in the same region as your functions and should never get very large (mine is 11 kB for 5 functions). I don&#x2019;t recommend touching the contents of this bucket as it could permanently corrupt your GCF objects.</p><p>Some Cloud Functions will also use Cloud Build which dumps artifacts into the <code>us.artifacts.&lt;project-id&gt;.appspot.com</code> bucket. See the <strong>The us.artifacts Bucket</strong> section below and what can be done to address these objects.</p><h4 id="the-app-engine-buckets">The App Engine Buckets</h4><p>The <code>staging.&lt;project-id&gt;.apopspot.com</code> bucket is used by the Google App Engine for <a href="https://cloud.google.com/appengine/docs/standard/php7/using-cloud-storage" rel="noopener">temporary storage</a> during deployments.</p><blockquote>App Engine also creates a bucket that it uses for temporary storage when it deploys new versions of your app. This bucket, named <code>staging.project-id.appspot.com</code>, is for use by App Engine only. Apps can&apos;t interact with this bucket.</blockquote><p>You can&#x2019;t get rid of this bucket but you can reduce the number of stored objects by <a href="https://cloud.google.com/sdk/gcloud/reference/app/deploy" rel="noopener">specifying a different bucket at build time</a> with the <code>&#x2014;bucket</code> flag. Here are the steps to ensure you incur minimal costs from this bucket:</p><ol><li>Create a new bucket with your desired <strong>regional storage</strong> (eg. gae_storage)&#x200A;&#x2014;&#x200A;if desired you can use a different bucket for each app</li><li>Use the&#x200A; <code>&#x2014;bucket</code> flag to specify where build objects should be saved when deploying your app</li></ol><pre><code>gcloud app deploy --bucket=gs://gae_storage</code></pre><p>3. Delete everything in the <code>staging.&lt;project-id&gt;.apopspot.com</code> directory except for the <code>ae/</code> folder</p><p>Once done the Multi-region <code>staging.&lt;project-id&gt;.apopspot.com</code> bucket will be minimally leveraged and your custom buckets will contain 99% of the objects for each app deployed.</p><p>App Engine deployments also leverage <code>us.artifacts.&lt;project-id&gt;.appspot.com</code> bucket. See the <strong>The us.artifacts Bucket</strong> section below and what can be done to address these objects.</p><h4 id="the-us-artifacts-bucket">The us.artifacts Bucket</h4><p>The &#xA0;<code>us.artifacts.&lt;project-id&gt;.appspot.com</code> bucket is used to store container images generated by the Cloud Build service. The only processes I have observed to generate objects in this bucket are Cloud Functions and App Engine builds. Objects generated by these processes are safe to remove post-deployment as described <a href="https://cloud.google.com/appengine/docs/standard/go/testing-and-deploying-your-app#managing_build_images" rel="noopener">here</a>.</p><blockquote>Once deployment is complete, App Engine no longer needs the container images. Note that they are not automatically deleted, so to avoid reaching your storage quota, you can safely delete any images you don&#x2019;t need.</blockquote><p>The same should apply for Cloud Function artifacts as well.</p><p>Although I do not use <strong>Firebase </strong>to deploy functions I have come across several open tickets online indicating that the approach below might cause issues for you. I might do another article exploring the Firebase issue and possible resolutions.</p><p><strong>Do not delete this bucket outright and do not follow the below instruction if you use Firebase to deploy functions!</strong></p><p>We cannot remove the bucket altogether but we can follow these steps to minimize space usage.</p><ol><li>Navigate to the LIFECYCLE tab of the <code>us.artifacts.&lt;project-id&gt;.appspot.com</code> bucket</li><li>Add a new lifecycle rule <strong>deleting </strong>objects that have an <strong>age </strong>greater than <strong>X</strong> days (I use <strong>7</strong> for mine)</li><li>Delete all objects in this bucket</li></ol><p>Once done you should see your space consumption for this bucket drop significantly. In my case, I was able to free up 85% of the utilized space to less than 300MB.</p><h3 id="conclusion"><strong>Conclusion</strong></h3><p>GCP is a great platform but when it comes to automatic storage of metadata objects and build container images things can get complicated and messy. Through this investigation, I got a chance to learn more about how Cloud Run, App Engine application, and Cloud Functions are managed. I hope that you were able to learn something from this post as well and if not that at least I was able to help you tidy up your GCS environment.</p><p><strong>Good luck and happy coding!</strong></p><p>Header photo by <a href="https://unsplash.com/@pedroplus?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Pedro da Silva</a> on <a href="https://unsplash.com/s/photos/bucket?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>]]></content:encoded></item><item><title><![CDATA[Don't Miss Out on Pub/Sub]]></title><description><![CDATA[A high-level overview of the Pub/Sub pattern and why you should be incorporating it into your projects.]]></description><link>http://www.theappliedarchitect.com/why-you-should-be-using-googles-pub-sub/</link><guid isPermaLink="false">6024a7e81d2c9700015eb97f</guid><category><![CDATA[GCP]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[pub/sub]]></category><category><![CDATA[event processing]]></category><category><![CDATA[big data]]></category><category><![CDATA[cloud]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Mon, 15 Feb 2021 18:54:39 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/02/rodion-kutsaev-xNdPWGJ6UCQ-unsplash--2-.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2021/02/rodion-kutsaev-xNdPWGJ6UCQ-unsplash--2-.jpg" alt="Don&apos;t Miss Out on Pub/Sub"><p>The <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern" rel="noopener">Pub/Sub pattern</a> is not something new but with the growing complexity of event systems combined with advances in distributed computing Pub/Sub is growing in popularity. In this article, I will explain the high-level Pub/Sub pattern and try to give you some reasons to include in your projects of varying sizes. </p><h2 id="without-pub-sub">Without Pub/Sub</h2><p>When building complex event-driven systems that require two or more different components to communicate with each other the traditional and simplest approach is to wire those components directly together. In some cases, this is done using web service APIs, flat-file exchange, or through shared data stores like databases. These approaches work but come with a set of challenges:</p><ol><li><strong>The modules become coupled together</strong> and a change to one component might require updates to any other components that it interacts with</li><li><strong>New integrations become time-consuming to build and test</strong></li><li>There is a <strong>general lack of scalability</strong> as the volume of events and number of integration points grows</li></ol><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2021/02/image-18.png" class="kg-image" alt="Don&apos;t Miss Out on Pub/Sub" loading="lazy"></figure><blockquote><em>Event for smaller hobby projects that involve just 2 or 3 components and very little data I find that this typical direct integration approach is extremely detrimental and discourages me from making iterative enhancements. I find it daunting that to add a new module I need to revisit and reconfigure several components.</em></blockquote><h2 id="the-pub-sub-pattern">The Pub/Sub Pattern</h2><p>Enter the Pub/Sub pattern. Instead of wiring parts of your infrastructure directly together the communication is done through a set of channels. A module in your infrastructure can either be a publisher to a channel to send events or a subscriber to read events. This presents several benefits and helps address some of the disadvantages outlined above. </p><ol><li><strong>The modules become completely decoupled</strong> from each other and their job when it comes to integration is to simply properly format and publish data, or receive payloads as subscribers and know how to process them</li><li><strong>Development of new components is simplified</strong> as developers don&#x2019;t need to worry about various integration points and the network layer</li><li><strong>Testing can be isolated and simplified</strong> by subscribing unit test scripts to topics or publishing simulated inputs</li><li>If the correct Pub/Sub solution is used your system should become <strong>infinitely scalable</strong> without touching any other components of your infrastructure</li></ol><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2021/02/image-19.png" class="kg-image" alt="Don&apos;t Miss Out on Pub/Sub" loading="lazy"></figure><h2 id="conclusion">Conclusion</h2><p>The Pub/Sub pattern is becoming the norm for building scalable and maintainable solutions. If you are not using this pattern as part of your infrastructure today I highly recommend at least evaluating it as an option as the benefits of reduced maintenance and future development costs would likely outweigh the effort needed to migrate your solution to this scalable pattern. As I mentioned about I highly recommend this pattern for even smaller hobby projects to improve the solution&apos;s future flexibility and to get experience with the Pub/Sub pattern. I use <a href="https://cloud.google.com/pubsub">Google&apos;s Pub/Sub</a> for most of my personal projects as it comes with 10GB of free data transfer per month which more than covers all my needs.</p>]]></content:encoded></item><item><title><![CDATA[Batching Jobs in GCP using the Cloud Scheduler and Functions]]></title><description><![CDATA[A walkthrough of how serverless batch jobs can be set up in the GCP platform using the Cloud Scheduler, Pub/Sub, and Cloud Functions.]]></description><link>http://www.theappliedarchitect.com/batching-jobs-in-gcp-using-the-cloud-scheduler-and-functions/</link><guid isPermaLink="false">601ed5fd1d2c9700015eb612</guid><category><![CDATA[GCP]]></category><category><![CDATA[batch]]></category><category><![CDATA[cloud functions]]></category><category><![CDATA[cron job]]></category><category><![CDATA[cloud scheduler]]></category><category><![CDATA[pub/sub]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Sun, 07 Feb 2021 14:59:25 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2021/02/lukas-blazek-UAvYasdkzq8-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2021/02/lukas-blazek-UAvYasdkzq8-unsplash.jpg" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions"><p>While designing and implementing solutions, I am often faced with the need to set up recurring batch jobs around data storage and processing. Recently I have been trying to keep my infrastructure as <a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener">serverless</a> as possible so in this article, I will show you how Google Cloud Platform can be leveraged to run almost any batch job your project might need <strong>for free</strong>.</p><h2 id="use-cases">Use Cases</h2><p>For me, this batch pattern is the most useful when it comes to data processing, reconciliation, and cleanup. Here is an example involving data aggregation&#x2026;</p><blockquote><em>A bucket can be an effective repository for streaming data but if your payloads are small in size and frequent&#x200A;&#x2014;&#x200A;having a file for every payload can get expensive if you have to do frequent reads. I solve this problem by running a batch job to merge individual payloads into hourly or daily files, allowing for much more cost effective solution.</em></blockquote><p>Or how about database cleanup&#x2026;</p><blockquote><em>If you have a SQL database containing large timeseries data sets, regular purging is critical for performance. You can squeeze a recurring job into a web application or the ETL system that is loading data into your tables however I solve for this using this serverless batch approach to decouple the solution and simplify maintenance.</em></blockquote><h2 id="architecture">Architecture</h2><p>We will be using 3 GCP services to implement our serverless batch solution. The <a href="https://cloud.google.com/scheduler" rel="noopener">Cloud Scheduler</a> will trigger our batch events, <a href="https://cloud.google.com/pubsub" rel="noopener">Pub/Sub</a> will be used to transmit the events to a <a href="https://cloud.google.com/functions" rel="noopener">Cloud Function</a> that will perform the required batch operation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-images-1.medium.com/max/800/1*Mxj97c0DCTmYtI-uRsc4fQ.png" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by&#xA0;author</figcaption></figure><h2 id="pricing">Pricing </h2><p>GCP offers a very generous free tier, I have made a simplified cost table below for the three services we will need to schedule and run a batch job. A batch job running every 5 minutes will use up 1 cloud scheduler job, ~9,000 Cloud Function executions, and ~9MB of Pub/Sub throughput.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/image.png" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>If you need more than 3 jobs across your projects, you will be charged 10 cents USD per month for every additional job.</p><h2 id="configuration">Configuration</h2><h3 id="pub-sub">Pub/Sub</h3><p>First, let&#x2019;s configure a Pub/Sub topic as it will be required in our set up of both the scheduler and serverless function. Topics can be configured <a href="https://console.cloud.google.com/cloudpubsub/topic/list" rel="noopener">here</a> in your GCP console.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/topic-creation-example-2.gif" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><ul><li>As you can see all we have to configure is the topic name - &#xA0;I am using <code>example-topic</code></li></ul><h3 id="cloud-scheduler">Cloud Scheduler</h3><p>Next, we configure the Cloud Scheduler as our batch trigger. Head <a href="https://console.cloud.google.com/cloudscheduler" rel="noopener">here</a> and create your scheduler job.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/cloud-scheduler-config-2.gif" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><ul><li>In my example, I am configuring the scheduler to run every 30 minutes but you can set any period desired</li><li>Specify the topic we created in the previous step&#x200A;&#x2014;&#x200A;I am using <code>example-topic</code></li><li>Our cloud function will not need anything except the trigger from the scheduler so the payload value is not important so you can put any value&#x200A;&#x2014;&#x200A; I am using <code>run</code></li></ul><h3 id="cloud-function">Cloud Function</h3><p>We are almost done, the last step is to create a Cloud Function that will be triggered when an event is triggered by the scheduler. You can find Cloud Function configuration <a href="https://console.cloud.google.com/functions/list" rel="noopener">here</a>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/function-creation-gif-2-1.gif" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><ul><li>Select Cloud Pub/Sub as the trigger type</li><li>Select the topic created in the first step</li><li>Proceed to the code configuration&#x200A;&#x2014;&#x200A;I will be using the out of the box Node.js function. The function simply logs the contents of the Pub/Sub payload.</li></ul><p>The function might take a minute to fully deploy.</p><blockquote><em>Keep in mind that you can use any of the available programming languages in this step.</em></blockquote><h3 id="testing">Testing</h3><p>To test our configuration we need to head over the <a href="https://console.cloud.google.com/cloudpubsub/topic/list" rel="noopener">Cloud Scheduler</a> list and manually trigger our scheduler using the <code>RUN NOW</code> option.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/image-6.png" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>To make sure our function successfully triggered we can head over to our <a href="https://console.cloud.google.com/functions/list" rel="noopener">Cloud Function</a> list, select the function you configured earlier and check out the <code>Logs</code> tab.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2021/02/image-7.png" class="kg-image" alt="Batching Jobs in GCP using the Cloud Scheduler and Functions" loading="lazy"><figcaption>Diagram by author</figcaption></figure><p>You should see log output indicating that your function ran and the payload messaged you configured for the Cloud Scheduler should also be displayed.</p><p><strong>Success!</strong></p><h2 id="conclusion">Conclusion</h2><p>There you have it, in 5 minutes we configured a 100% free solution that you can use to run various types of batch jobs. If you ever find yourself in need of quickly setting up a highly decoupled solution for kicking off or running batch jobs you now have a quick and easy way to get it done using GCP.</p><p><strong>Good luck and happy coding!</strong></p>]]></content:encoded></item><item><title><![CDATA[Setting up HTTPS for your blog or web application with certbot]]></title><description><![CDATA[Step by step guide for setting up SSL for your blog (Ghost) or web app using certbot and serving certificates using NGINX proxy.]]></description><link>http://www.theappliedarchitect.com/setting-up-https-for-your-blog-certbot/</link><guid isPermaLink="false">5e5bf962de202d00013e5b1c</guid><category><![CDATA[certbot]]></category><category><![CDATA[nginx]]></category><category><![CDATA[ghost]]></category><category><![CDATA[node.js]]></category><category><![CDATA[SSL]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Mon, 02 Mar 2020 04:58:13 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2020/03/cert-nginx-1.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2020/03/cert-nginx-1.png" alt="Setting up HTTPS for your blog or web application with certbot"><p>This post will go over the steps needed to set up an HTTPS certificate for you web application using certbot and an NGINX proxy.</p><p>The web application in my case is ghost (node.js content management system) but the below should work for any web application as long as you have the knowledge and capability to expose your web application on ports other then 80 and 443.</p><hr><p><strong>Step 1 - set up certbot on your server and generate a certificate </strong></p><p>If your web application does not have an HTTPS certificate most browsers will block users and display something like the following message.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2020/03/image-3.png" class="kg-image" alt="Setting up HTTPS for your blog or web application with certbot" loading="lazy"></figure><p>This happens because your web application does not have a valid and registered HTTPS certificate. Luckily these can be generated quite easily using free utilities, one of these is <a href=" https://certbot.eff.org/">certbot</a>. Certbot is a tool you can download on your server and in a few seconds register your domain and generate a valid certificate. </p><p>On the <a href=" https://certbot.eff.org/">certbot</a> site you can choose a type of web server and operating system you are running and the site will give you a set of step by step instructions for generating a certificate. In this example I will use <strong>Ubuntu 18.04</strong> as it is the most popular Linux OS. For the server I will use <strong>none of the above </strong>as this is the simplest and most dynamic form of certificate generation. Here were my instructions as presented by certbot.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2020/03/image-4.png" class="kg-image" alt="Setting up HTTPS for your blog or web application with certbot" loading="lazy"></figure><p>I installed certbot as instructed using the below.</p><!--kg-card-begin: markdown--><pre><code>sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot
</code></pre>
<!--kg-card-end: markdown--><p>Instead of playing with the webroot of my web application I recommend stopping your web server and running the standalone option. Before proceeding ensure your web application is stopped. You can validate that you have successfully done this using: <code>sudo netstat -ltnp | grep -w &apos;80&apos;</code>. If this returns anything your application is running and as a last resort you can kill it with the <code>kill pid</code> command.</p><p>When generating the certificate you will be asked for the domain and subdomains names you are registering. Even if you don&apos;t have any subdomains you should still register the www subdomain. &#xA0;In my case I will be registering theappliedarchitect.com and www.theappliedarchitect.com. You should see the following if the certification was successful.</p><!--kg-card-begin: markdown--><pre><code>$ sudo certbot certonly --standalone
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Please enter in your domain name(s) (comma and/or space separated)  (Enter &apos;c&apos;
to cancel): theappliedarchitect.com www.theappliedarchitect.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for theappliedarchitect.com
http-01 challenge for www.theappliedarchitect.com
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/theappliedarchitect.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/theappliedarchitect.com/privkey.pem
   Your cert will expire on 2020-05-30. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   &quot;certbot renew&quot;
 - If you like Certbot, please consider supporting our work by:
   Donating to ISRG / Let&apos;s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

</code></pre>
<!--kg-card-end: markdown--><p>If you see the below port 80 is being used by another application, follow the instruction above to identify the culprit and terminate it.</p><!--kg-card-begin: markdown--><pre><code>$ sudo certbot certonly --standalone
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Please enter in your domain name(s) (comma and/or space separated)  (Enter &apos;c&apos;
to cancel): theappliedarchitect.com www.theappliedarchitect.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for theappliedarchitect.com
http-01 challenge for www.theappliedarchitect.com
Cleaning up challenges
Problem binding to port 80: Could not bind to IPv4 or IPv6.
</code></pre>
<!--kg-card-end: markdown--><p>At this point the certificates should be on your server with the location indicated by the certbot output. In my case they are in the /etc/letsencrypt/live/theappliedarchitect.com/ folder.</p><p>The next step will cover setting up a proxy to listen on port 443 and 80, provide the correct certificate and direct traffic to the correct web application. You don&apos;t need to continue if you are happy setting up generated certificate directly on your web app.</p><p><strong>Step 2 - set up NGINX </strong></p><p>If you like me have several web applications running on the same server or if you want to standardize your certificate setup process regardless of web application type. Start by installing NGINX (if its not already included in your distribution) <code>sudo apt install nginx</code></p><p>You can verify the install by using a browser to navigate to the domain you just registered and you should see something like the below.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2020/03/image-5.png" class="kg-image" alt="Setting up HTTPS for your blog or web application with certbot" loading="lazy"></figure><p>NGINX routing is managed using configuration files. These are usually managed in the <code>/etc/nginx/sites-available</code> directory and deployed as links to the <code>/etc/nginx/sites-enabled</code> folder. You will noticed that there is a default configuration file present by default, we will start by removing the link from the <code>site-enabled</code> folder.</p><!--kg-card-begin: markdown--><pre><code>sudo rm /etc/nginx/sites-enabled/default
</code></pre>
<!--kg-card-end: markdown--><p>Next we need to create a new configuration file to direct both HTTP and HTTPS traffic to our web application. I will call my config file <code>blog</code> but you can use any name that is relevant for your purpose. Create the file and link it to the enabled folder.</p><!--kg-card-begin: markdown--><pre><code>sudo touch /etc/nginx/sites-available/blog
sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/blog
</code></pre>
<!--kg-card-end: markdown--><p>These configuration files can get very <a href="https://www.nginx.com/resources/wiki/start/topics/examples/full/">complicated</a> but we will keep ours bare bones. The file will have two server mappings, one for port 80 (HTTP) and one for port 443 (HTTPS) where we will link our newly generated certificate. Make sure you replace all instances of <code>theappliedarchitect.com</code> with your domain name. Note the SSL certificate file paths, if you moved yours from the defautl folders you might need to update these as well. My blog runs on port <code>2368</code> make sure you update the port to reflect the internal port your web application will be running on. </p><!--kg-card-begin: markdown--><pre><code>server {
    listen 0.0.0.0:80;
    server_name theappliedarchitect.com;
    access_log /var/log/nginx/theappliedarchitect.com.log;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header HOST $http_host;
        proxy_set_header X-NginX-Proxy true;

        proxy_pass http://127.0.0.1:2368;
        proxy_redirect off;
    }
}



server {
        server_name theappliedarchitect.com;
        listen 443 ssl;

        location / {
                proxy_pass      http://127.0.0.1:2368;
                proxy_set_header    X-Real-IP $remote_addr;
                proxy_set_header    Host      $http_host;
                proxy_set_header X-Forwarded-Proto https;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        }

        ssl_certificate     /etc/letsencrypt/live/theappliedarchitect.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/theappliedarchitect.com/privkey.pem;
        ssl on;

}
</code></pre>
<!--kg-card-end: markdown--><p>All you have to do now is restart the NGINX service and it should both provide the certificate and direct traffic to your underlying application.</p><!--kg-card-begin: markdown--><pre><code>sudo service nginx restart
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2020/03/image-6.png" class="kg-image" alt="Setting up HTTPS for your blog or web application with certbot" loading="lazy"><figcaption>Success!</figcaption></figure><p>One last thing to mention is that the certificates provided by certbot are only valid for 3 months and must be renewed before expiry (the drawback of using a free service). Certbot will send you reminder emails to renew your certificate when the time comes. In a future post I will outline how this renewal can be automated!</p>]]></content:encoded></item><item><title><![CDATA[TensorFlow + Docker MNIST Classifier - The User Interface (Angular)]]></title><description><![CDATA[Angular + Docker front end implementation to utilize the TensorFlow generated MNIST classification models.]]></description><link>http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-the-user-interface/</link><guid isPermaLink="false">5dd4528e66435b0001f6b785</guid><category><![CDATA[tensorflow]]></category><category><![CDATA[docker]]></category><category><![CDATA[angular]]></category><category><![CDATA[neural network]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[MNIST]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Wed, 20 Nov 2019 01:58:17 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2019/11/d---a.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2019/11/d---a.png" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)"><p>In this post I will be going through the process of setting up an Angular front end to connect and utilize some of the TensorFlow models that were set up in previous posts. The model set up and training walk through can be found <a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/"><strong>here</strong></a><strong> </strong>and the docker serving walk through <a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/"><strong>here</strong></a>. This post is part of the <a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/"><strong>TensorFlow + Docker MNIST Classifier</strong></a><strong> </strong>series.</p><blockquote>If you are not familiar with Angular I highly recommend at least going through the official getting started <a href="https://angular.io/start"><strong>tutorial</strong> </a>before implementing any of the code below. Or you can use your own front end instead of Angular. </blockquote><p>This is not an Angular tutorial and I will not be going through the code in detail. We will be cloning a project from my git repository and going through some of the key components that are specific to this project. To keep things organized we will be running our Angular application within a docker container. </p><p>For reference, <strong><a href="http://examples.theappliedarchitect.com/">here</a> </strong>is the final result we are targeting. </p><p><strong>The setup</strong></p><!--kg-card-begin: markdown--><p><mark>If you are using Docker on windows you might need to share your Drives. This can be done by navigating to Docker settings &gt; Shared Drives and making sure the drives you are working with are checked</mark></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>We can start by cloning my Angular repository:</p>
<pre><code>$ git clone https://github.com/adidinchuk/angular-mnist-project
</code></pre>
<p>The app folder structure should look like this:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>&#x251C;&#x2500;&#x2500; src<br>
&#x2502;   &#x251C;&#x2500;&#x2500; app<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; components<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; digits<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; canvas<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; digit-control<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; prediction<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x2502;.......<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; services<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; digits<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; api<br>
&#x2502;   &#x2502;   &#x2502;.......<br>
&#x2502;   &#x251C;&#x2500;&#x2500; assets<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; css<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; js<br>
&#x2502;   &#x251C;&#x2500;&#x2500; environments<br>
&#x2502;   &#x2502;   &#x2502;.......<br>
&#x251C;&#x2500;&#x2500; proxy.config.json<br>
&#x2502;.......</p>
<!--kg-card-end: markdown--><p>Some key objects and their responsibilities:</p><!--kg-card-begin: markdown--><p><code>src/app/components/digits/canvas</code><br>
Canvas object used for several things - primarely for getting user digit input, image scaling and displaying autoencoder results<br>
<code>src/app/components/digits/digit-control</code><br>
Main component responsible for collecting user input (both noise option and digit)<br>
<code>src/app/components/digits/prediction</code><br>
Very basic component responsible for displaying the classifier results</p>
<p><code>src/app/services/digits/api</code><br>
Services used for communication with the TF serving instance</p>
<!--kg-card-end: markdown--><p>Let&apos;s take a closer look at how we access our TF serving endpoints. Here is what our main service methods look like:</p><!--kg-card-begin: markdown--><pre><code class="language-javascript">//Autoencoder endpoint to clean data before classification processing
  runAutoencoder(data): Observable&lt;any&gt; {
    return this.http.post(DigitsConfig.API_ENDPOINT_PROXY + DigitsConfig.AUTOENCODER_MODEL + &apos;:&apos; + DigitsConfig.TF_METHOD_NAME, {
      &quot;instances&quot;: [data]
    })
  }

  //classification endpoint to classify a 784 vector into a 0-9 digit
  runClassification(data): Observable&lt;any&gt; {
    return this.http.post(DigitsConfig.API_ENDPOINT_PROXY + DigitsConfig.CLASSIFICATION_MODEL + &apos;:&apos; + DigitsConfig.TF_METHOD_NAME, {
      &quot;instances&quot;: [data]
    })
  }
</code></pre>
<!--kg-card-end: markdown--><p>And the configuration attributes:</p><!--kg-card-begin: markdown--><pre><code>public static API_ENDPOINT_PROXY = &apos;/v1/models/&apos;;

public static AUTOENCODER_MODEL = &apos;autoencoder&apos;;
public static CLASSIFICATION_MODEL = &apos;classifier&apos;;

public static TF_METHOD_NAME = &apos;predict&apos;;
public static TF_INPUT_PARAM_NAME = &apos;instances&apos;;
</code></pre>
<!--kg-card-end: markdown--><p>Note that we are using a proxy for the mapping to ensure that any networking changes can be made outside the source code. For this we have a <code>proxy.config.json</code> file in the root directory with the following content:</p><!--kg-card-begin: markdown--><pre><code>{
    &quot;/v1/*&quot;: {
        &quot;target&quot;: &quot;http://SERVING:8501&quot;,
        &quot;secure&quot;: false,
        &quot;logLevel&quot;: &quot;debug&quot;,
        &quot;changeOrigin&quot;: true
    }
}
</code></pre>
<!--kg-card-end: markdown--><p>Bellow I will be covering exactly what <code>SERVING</code> means and how we force the Angular application to adhere to this proxy. But in the mean time from our components we can leverage these endpoints using something like this:</p><!--kg-card-begin: markdown--><pre><code>this.api.runClassification(data).subscribe(
  class_res =&gt; {
    this.prediction.digit = this.extractPrediction(class_res.predictions[0]);
  },
  class_err =&gt; {
    console.log(&quot;Error occured during the classification call.&quot;);
})
</code></pre>
<!--kg-card-end: markdown--><p>Fairly straightforward, in fact due to my lacking front end knowledge the majority of my time went towards figuring out how to use canvases correctly to collect user input and display the final result. The TF serving integration was the easy part. &#xA0;I am certain there are many ways my components could be improved, feel free to clone my repository and go nuts. </p><p><strong>Running the docker container </strong></p><p>Before building and launching the docker instance let&apos;s take a quick look at the <code>Dockerfile</code>, you will notice that the application on start sets up the <code>proxy.config.json</code> file using that we took a look at above using <code>--proxy-config proxy.config.json</code>.</p><!--kg-card-begin: markdown--><pre><code># base image
FROM node:8.15.0

# install chrome for protractor tests
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN sh -c &apos;echo &quot;deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main&quot; &gt;&gt; /etc/apt/sources.list.d/google.list&apos;
RUN apt-get update &amp;&amp; apt-get install -yq google-chrome-stable

# set working directory
WORKDIR /app

# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package.json /app/package.json
RUN npm install

# add app
COPY . /app

# start app
CMD ng serve --host 0.0.0.0 --proxy-config proxy.config.json
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Before we run the container we need to build the image and make sure all dependencies are retrieved. We can do this using:</p>
<pre><code>$ docker build -t angular/mnist .\angular-mnist-project
</code></pre>
<p>Depending on your network&apos;s performance this could take a few minutes as all required dependencies will be retrieved. Notice that we tagged the image angular/mnist, you can also see this in the output:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-29.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)" loading="lazy"></figure><!--kg-card-begin: markdown--><p>Now that we have our image we can run the docker container using:</p>
<pre><code>$ docker run --rm -p 4201:4200 --name angular angular/mnist
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>Breaking the command down:</p>
<p><code>--rm</code><br>
make sure the container is automatically cleaned up on exit<br>
<code>-p 4201:4200</code><br>
map the docker internal port 4200 to the external port 4201<br>
<code>--name angular</code><br>
serving give a name to our container for easier identification and termination<br>
<code>angular/mnist</code><br>
assosiate the image we built above</p>
<!--kg-card-end: markdown--><p>After running the command if you open <a href="http://localhost:4201"><strong>http://localhost:4201</strong></a> in your browser you should see the running application.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-30.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)" loading="lazy"></figure><p>However if you try submitting the form you will see that nothing happens and taking a look at the console logs you can see that the TF serving endpoint is timing out.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-31.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)" loading="lazy"></figure><p>This is happening because the angular docker container and the TF serving docker container are isolated. While from from your machine you can see both <code>localhost:4201</code> and <code>localhost:8501</code>, the images are not able to resolve these paths. The solution is to use <strong><a href="https://docs.docker.com/network/">docker network</a></strong> functionality. We do this by first creating a docker network and then updating our docker start commands to first associate the containers with the network and then associate unique aliases to them. This aliases will allow containers to easily reference each other. &#xA0;</p><!--kg-card-begin: markdown--><p>Create a new docker network</p>
<pre><code>$ docker network create MNIST
</code></pre>
<p>Remember to kill and remove any containers that are still active before running</p>
<pre><code>$ docker kill angular
$ docker rm angular
$ docker kill serving
$ docker rm serving
</code></pre>
<p>Update and run the Angular docker run scripts</p>
<pre><code>$ docker run --net MNIST --net-alias=ANGULAR --rm -p 4201:4200 --name angular angular/mnist
</code></pre>
<p>Update and run the TF Serving docker run scripts (<strong><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/">original</a></strong>)</p>
<pre><code>$ set TF_MODEL_DIR=%cd%/serving/mnist
$ docker run --net MNIST --net-alias=SERVING --rm -p 8501:8501 --name serving --mount type=bind,source=%TF_MODEL_DIR%/classifier,target=/models/classifier --mount type=bind,source=%TF_MODEL_DIR%/autoencoder,target=/models/autoencoder --mount type=bind,source=%TF_MODEL_DIR%/config,target=/config tensorflow/serving --model_config_file=/config/models.config
</code></pre>
<p>Associating the alias <code>SERVING</code> with <code>--net-alias=SERVING</code> to the TF Serving container allows the Angular application to access the endpoints at <code>SERVING:8501</code> as both are running on the <code>MNIST</code> network.</p>
<!--kg-card-end: markdown--><p>With this done the <a href="http://localhost:4201"><strong>http://localhost:4201</strong></a><strong> </strong>application should now work as expected:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-32.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)" loading="lazy"></figure><p>If you check the terminal where the Angular docker container is running (or <code>docker angular logs</code>) you can see that requests are being correctly forwarded to <code>http://SERVING:8501</code>:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-33.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The User Interface (Angular)" loading="lazy"></figure><p>That&apos;s it! Hope this was helpful to somebody out there, I know I learned a low implementing and documenting this project. </p><p><strong>Here is a summary of the components involved in this project:</strong></p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Section</th>
<th style="text-align:center">Git Repository</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/">Introduction</a></td>
<td style="text-align:center">N/A</td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/">The Models</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-mnist-project">tf-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/">Serving Models</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-serving-mnist-project">tf-serving-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-the-user-interface/">The User Interface</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/angular-mnist-project">angular-mnist-project</a></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[TensorFlow + Docker MNIST Classifier - Serving Models]]></title><description><![CDATA[Setting up Google's TensorFlow serving application and hosting multiple models.]]></description><link>http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/</link><guid isPermaLink="false">5dd4210566435b0001f6b638</guid><category><![CDATA[tensorflow]]></category><category><![CDATA[google cloud platform]]></category><category><![CDATA[tf]]></category><category><![CDATA[tensorflow serving]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[MNIST]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Tue, 19 Nov 2019 20:27:03 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2019/11/tf-container-2.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2019/11/tf-container-2.png" alt="TensorFlow + Docker MNIST Classifier - Serving Models"><p>This post will be covering the process of setting up TensorFlow serving and exposing the two models that were build and trained in the previous post. TensorFlow <a href="https://www.tensorflow.org/tfx/serving/architecture"><strong>serving</strong></a><strong> </strong>is a system for managing machine learning models and exposing them to consumers via a standardized API. This post is part of the <a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/"><strong>TensorFlow + Docker MNIST Classifier</strong></a><strong> </strong>series.</p><blockquote>If you are not familiar with docker I highly recommend going through the official getting started <a href="https://docs.docker.com/docker-for-windows/"><strong>tutorial</strong></a> before implementing any of the code below.</blockquote><blockquote>For all of my API testing I will be using the postman application. You can use your own testing tool or download postman <a href="https://www.getpostman.com/"><strong>here</strong></a>.</blockquote><p>One of the most practical ways of setting up TensorFlow is via Google&apos;s pre-built docker container and this is the approach that will be taken in this post.</p><p><strong>Set up the basic docker image</strong></p><p>The first step is to ensure we have a docker serving image working correctly on our machine using one of the out of the box testing models. Make sure you have docker installed before running the below scripts in your command line.</p><!--kg-card-begin: markdown--><p><mark>If you are using Docker on windows you might need to share your Drives. This can be done by navigating to Docker settings &gt; Shared Drives and making sure the drives you are working with are checked</mark></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>download the docker image and clone the repository<br>
<code>$ docker pull tensorflow/serving</code><br>
<code>$ git clone https://github.com/tensorflow/serving</code></p>
<!--kg-card-end: markdown--><p><em><u>Linux - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>#map the path to the test models 
$ TESTDATA=&quot;$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata&quot; 
$ docker run --rm -p 8501:8501 \ --name serving \  --mount type=bind,source=$TESTDATA/saved_model_half_plus_two_cpu,target=/models/half_plus_two \ -e MODEL_NAME=half_plus_two</code></pre>
<!--kg-card-end: markdown--><p><em><u>Windows - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>#map the path to the test models 
$ set TESTDATA=%cd%/serving/tensorflow_serving/servables/tensorflow/testdata 
$ docker run --rm -p 8501:8501 --name serving --mount type=bind,source=%TESTDATA%/saved_model_half_plus_two_cpu,target=/models/half_plus_two -e MODEL_NAME=half_plus_two</code></pre>
<!--kg-card-end: markdown--><p>The serving application should not be running in the docker instance and exposed to your network on port <code>8501</code>. You should see something like this if your container has launched successfully:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-22.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>Now lets check if the model is up and running and what it looks like. I will be using postman to make some requests and analyze the responses. Let&apos;s send a sample request and see if the model works. In this case the endpoint is <code>localhost:8501/v1/models/<u>&lt;model name&gt;</u>:predict</code>, where the <code>&lt;model name&gt;</code> is half_plus_two. In order for the model to serve a prediction it requires for the feature matrix to be provided in the body of the <code>POST</code> request. Here is the payload format in this case: <code>{&quot;instances&quot;: <u>&lt;features&gt;</u>}</code> where <code>&lt;features&gt;</code> is an array containing all the sample you are looking to classify. The model we are working with has input and output dimensions of 1 so we will use <code>{&quot;instances&quot;: [1.0, 2.0]}</code> as our payload and we expect a result of 2.5 and 3.0 (x / 2 + 2).</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th></th>
<th style="text-align:center"></th>
</tr>
</thead>
<tbody>
<tr>
<td>POST endpoint</td>
<td style="text-align:center"><code>localhost:8501/v1/models/half_plus_two:predict</code></td>
</tr>
<tr>
<td>Expected response</td>
<td style="text-align:center"><code>{&quot;instances&quot;: [1.0, 2.0]}</code></td>
</tr>
<tr>
<td>Payload</td>
<td style="text-align:center"><code>{&quot;predictions&quot;: [2.5, 3.0]}</code></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Submitting the request using postman we can see that the model is exposed and working exactly as expected!</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-23.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>Before proceeding we want to make sure clean up docker container as we will be redeploying our own models in a few minutes. Run the following:</p><p><code>$ docker kill serving</code></p><p><strong>Deploy a custom model</strong></p><p>Next we need to serve a model that does something more useful then the out of the box <code>half_plus_two</code> model. Before proceeding make sure you have a TensorFlow .pb models ready to go. In my previous post I set up an auto encoder and a classifier for processing MNIST images, you can take a look at the <strong><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/">post</a> </strong>or grab the source code <a href="https://github.com/adidinchuk/tf-mnist-project"><strong>here</strong></a>.</p><p>My current working directory currently contains both my serving repository and my TensorFlow project folder and looks like this:</p><!--kg-card-begin: markdown--><p>&#x251C;&#x2500;&#x2500; tf-mnist-project<br>
&#x2502;   &#x251C;&#x2500;&#x2500; src<br>
&#x2502;   &#x2502;.......<br>
&#x251C;&#x2500;&#x2500; serving<br>
&#x2502;   &#x251C;&#x2500;&#x2500; tensorflow_serving<br>
&#x2502;   &#x2502;.......</p>
<!--kg-card-end: markdown--><p>You might have to adjust my commands to reflect your own folder structure.</p><p>Let&apos;s create a new serving folder in the serving repository to house our models and a model subdirectory for our first model.</p><!--kg-card-begin: markdown--><p><code>$ mkdir serving\mnist</code><br>
<code>$ mkdir serving\mnist\autoencoder</code></p>
<!--kg-card-end: markdown--><p>You should now see the 2 newly created folder. Now let&apos;s copy our first model into the model subdirectory.</p><p><code>$ cp tf-mnist-project\models\autoencoder\production\1 serving\mnist\autoencoder -r</code></p><p>Our folder structure should now look something like this:</p><!--kg-card-begin: markdown--><p>&#x251C;&#x2500;&#x2500; tf-mnist-project<br>
&#x2502;   &#x251C;&#x2500;&#x2500; src<br>
&#x2502;   &#x2502;.......<br>
&#x251C;&#x2500;&#x2500; serving<br>
&#x2502;   &#x251C;&#x2500;&#x2500; tensorflow_serving<br>
&#x2502;   &#x251C;&#x2500;&#x2500; mnist<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; autoencoder<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; 1<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; assets<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; variables<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; saved_model.pb<br>
&#x2502;   &#x2502;.......</p>
<!--kg-card-end: markdown--><p>Let&apos;s try mounting the new model and launching the docker container.</p><p><em><u>Linux - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>#update the path to our new model 
$ TESTDATA=&quot;$(pwd)/serving/mnist&quot; 
$ docker run --rm -p 8501:8501 \ --name serving \  --mount type=bind,source=$TESTDATA/autoencoder,target=/models/autoencoder \ -e MODEL_NAME=autoencoder</code></pre>
<!--kg-card-end: markdown--><p><em><u>Windows - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>#map the path to the new model 
$ set TESTDATA=%cd%/serving/mnist 
$ docker run --rm -p 8501:8501 --name serving --mount type=bind,source=&quot;%TESTDATA%&quot;/autoencoder,target=/models/autoencoder -e MODEL_NAME=autoencoder</code></pre>
<!--kg-card-end: markdown--><p>Lets pause here and make sure we understand what we are asking docker to do with our command, because we are about to run into a problem trying to expose both of our models simultaneously.</p><!--kg-card-begin: markdown--><p><code>--rm</code><br>
make sure the container is automatically cleaned up on exit<br>
<code>-p 8501:8501</code><br>
map the docker internal port 8501 to the external port 8501<br>
<code>--name</code><br>
serving give a name to our container for easier identification and termination<br>
<code>--mount type=bind,source=$TESTDATA/autoencoder,target=/models/autoencoder</code><br>
mounts the content of the autoencoder folder, this is required for the serving application to locate the correct model<br>
<code>-e MODEL_NAME=autoencoder</code><br>
pass the environment variable MODEL_NAME to the serving application to help locate the correct model</p>
<!--kg-card-end: markdown--><p>Hopefully after launching the latest docker container with the above command you see an output without any errors, indicating that our custom model is up and running correctly. Again we will be confirming this with a postman request. This time the <code>POST</code> endpoint <code>localhost:8501/v1/models/autoencoder:predict</code> and the feature vector should have the dimension [1, 784] (28x28 pixels). I made a sample payload of 784 1.0 values, you can grab it bellow.&#x200C; &#xA0;</p><p><a href="https://raw.githubusercontent.com/adidinchuk/tf-mnist-project/master/sample">Here is the sample digit I used.</a> &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;</p><p>After sending a request via postman we can see that the model is up, running and returning a [1, 784] response as expected!</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-25.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>We can even plot the digits to visualize the result using a function like this:</p><!--kg-card-begin: markdown--><pre><code class="language-python">import matplotlib.pyplot as plt
import numpy as np

def compare_digits(raw, processed):
  image = np.append(raw, processed)
  image = np.array(image, dtype=&apos;float&apos;)
  pixels = image.reshape((56, 28))
  plt.imshow(pixels, cmap=&apos;gray&apos;)
  plt.show()
</code></pre>
<!--kg-card-end: markdown--><p>And should see something like this:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-26.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>Again before moving on we want to make sure clean up docker container:</p><p><code>$ docker kill serving</code></p><p><strong>Deploy multiple custom models </strong><b>simultaneously</b></p><p>Our serving application is able to serve us one model but in a production environment we would typically expect for multiple models to be available at the same time. You will notice in our original <code>docker run</code> command we mounted our model folder and passed <code>-e MODEL_NAME=&lt;model&gt;</code>. Mounting multiple model folders can be done without issue but passing multiple model names cannot be done directly in this request. To bypass this challenge we can store our model information in a configuration file and providing it to the serving application.</p><p>Before creating the config file we need to add the folders for the second model and one to store the config file and copy the build classifier model:</p><!--kg-card-begin: markdown--><p><code>$ mkdir serving\mnist\classifier</code><br>
<code>$ mkdir serving\mnist\config</code><br>
<code>$ cp tf-mnist-project\models\classifier\production\1 serving\mnist\classifier -r</code></p>
<!--kg-card-end: markdown--><p>Now lets create a <code>models.config</code> file in the config directory. This is what it should look like:</p><!--kg-card-begin: markdown--><pre><code>model_config_list: { 
  config: {
    name: &quot;classifier&quot;,
    base_path: &quot;/models/classifier&quot;,
    model_platform: &quot;tensorflow&quot;
  },
  config: {
    name: &quot;autoencoder&quot;,
    base_path: &quot;/models/autoencoder&quot;,
    model_platform: &quot;tensorflow&quot;
  },
}
</code></pre>
<!--kg-card-end: markdown--><p>The new folder structure should now look like this:</p><!--kg-card-begin: markdown--><p>&#x251C;&#x2500;&#x2500; tf-mnist-project<br>
&#x2502;   &#x251C;&#x2500;&#x2500; src<br>
&#x2502;   &#x2502;.......<br>
&#x251C;&#x2500;&#x2500; serving<br>
&#x2502;   &#x251C;&#x2500;&#x2500; tensorflow_serving<br>
&#x2502;   &#x251C;&#x2500;&#x2500; mnist<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; autoencoder<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; 1<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; assets<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; variables<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; saved_model.pb<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; classifier<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; 1<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; assets<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; variables<br>
&#x2502;   &#x2502;   &#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; saved_model.pb<br>
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; config<br>
&#x2502;   &#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; models.config<br>
&#x2502;   &#x2502;.......</p>
<!--kg-card-end: markdown--><p>Lets launch docker again, this time mounting both model folders along with the configuration file.</p><p><em><u>Linux - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>$ TF_MODEL_DIR=&quot;$(pwd)/serving/mnist&quot; 
$ docker run \
--rm -p 8501:8501 --name serving \
--mount type=bind,source=%TF_MODEL_DIR%/classifier,target=/models/classifier \
--mount type=bind,source=%TF_MODEL_DIR%/autoencoder,target=/models/autoencoder \
--mount type=bind,source=%TF_MODEL_DIR%/config,target=/config  tensorflow/serving \
--model_config_file=/config/models.config 
</code></pre>
<!--kg-card-end: markdown--><p><em><u>Windows - launching the container</u></em></p><!--kg-card-begin: markdown--><pre><code>$ set TF_MODEL_DIR=%cd%/serving/mnist 
$ docker run --rm -p 8501:8501 --name serving --mount type=bind,source=%TF_MODEL_DIR%/classifier,target=/models/classifier --mount type=bind,source=%TF_MODEL_DIR%/autoencoder,target=/models/autoencoder --mount type=bind,source=%TF_MODEL_DIR%/config,target=/config tensorflow/serving --model_config_file=/config/models.config</code></pre>
<!--kg-card-end: markdown--><p>Notice we are mounting 3 folders, one for each model and one that stores our configuration file. We then tell the module to use the <code>model.config</code> from the mounted config folder to figure out how to map our models to the other 2 folders using <code>--model_config_file=/config/models.config</code>. The output should look like the below (take note that there both of our models are now up and running.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-27.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>We now have two models accessible on:</p><!--kg-card-begin: markdown--><p><code>localhost:8501/v1/models/autoencoder:predict</code><br>
<code>localhost:8501/v1/models/classifier:predict</code></p>
<!--kg-card-end: markdown--><p>If you rerun the autoencoder test from above you will see that the model is still functional, and if you forward the response to the classify endpoint you should see something like this:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-28.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - Serving Models" loading="lazy"></figure><p>As you can see the classifier model is also up and running and has classified our <a href="https://raw.githubusercontent.com/adidinchuk/tf-mnist-project/master/sample"><strong>sample</strong></a><strong> </strong>3 digit correctly. </p><p>I converted my serving module into a personal repository, you can check it out <a href="https://github.com/adidinchuk/tf-serving-mnist-project"><strong>here</strong></a><strong> </strong>(if you clone my repository you will need to change some of the path values as the root folder name will no longer be <code>serving</code>). Now that we have our models up and running the next step is to set up a basic external application to utilize the API endpoints and demonstrate the functionality. &#xA0;</p><p><strong>Here is a summary of the components involved in this project:</strong></p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Section</th>
<th style="text-align:center">Git Repository</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/">Introduction</a></td>
<td style="text-align:center">N/A</td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/">The Models </a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-mnist-project">tf-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/">Serving Models</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-serving-mnist-project">tf-serving-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-the-user-interface/">The User Interface</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/angular-mnist-project">angular-mnist-project</a></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[TensorFlow + Docker MNIST Classifier - The Models]]></title><description><![CDATA[TensorFlow neural network implementation and training for classifying MNIST hand written images.]]></description><link>http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/</link><guid isPermaLink="false">5dbb2bbebfbaa20001b1e083</guid><category><![CDATA[tensorflow]]></category><category><![CDATA[autoencoder]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[neural network]]></category><category><![CDATA[MNIST]]></category><dc:creator><![CDATA[TAA]]></dc:creator><pubDate>Thu, 31 Oct 2019 19:30:23 GMT</pubDate><media:content url="http://www.theappliedarchitect.com/content/images/2019/10/tf.png" medium="image"/><content:encoded><![CDATA[<img src="http://www.theappliedarchitect.com/content/images/2019/10/tf.png" alt="TensorFlow + Docker MNIST Classifier - The Models"><p>This post will be covering the two models that were set up in TensorFlow to process MNIST digit data, how training was conducted and finally how the results were converted into a tangible model to be leveraged down stream. This post is part of the <a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/"><strong>TensorFlow + Docker MNIST Classifier</strong></a><strong> </strong>series.</p><blockquote>I will not be covering the basics of TensorFlow in these posts. Typically I am not a huge fan of programming literature myself with the massive amount of resources available online, however for learning TensorFlow I highly recommend <a href="https://www.packtpub.com/big-data-and-business-intelligence/tensorflow-machine-learning-cookbook"><strong>this</strong></a><strong> </strong>e-book for grasping the fundamentals.</blockquote><p><u><em>The Data Set (MNIST)</em></u>: <strong><a href="http://yann.lecun.com/exdb/mnist/">This</a> </strong>is one of the most popular machine learning data sets on the internet at the moment. It consists of tens of thousands of 28 x 28 labeled hand written written digits like the one below.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/digits.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The Models" loading="lazy"></figure><p>One of the key success criteria for this project was the use of multiple models in the final solution. The first model will be an auto-encoder to standardize the image data and the second model will classify it.</p><p><strong>Features and targets</strong></p><p>The features or digits will be passed through the model as a 784 dimensional vectors with each element of the vector representing pixel intensity (white to black) of each pixel in the 28 x 28 image. Scaling was used on the feature data to improve performance converting the value range from [0.0, 255.0] to [0.0, 1.0] by dividing each value by 255.</p><p>Data set labels (targets) are a single dimensional vector with values ranging from 0 - 9, representing the 10 potential digit classes. In order to improve model performance and simplicity these were transformed into a 10 dimensional<strong> <a href="https://en.wikipedia.org/wiki/One-hot">one-hot representation</a></strong> with each dimension representing the probability of the associated digit e.g. [5] -&gt; [0, 0, 0, 0, 0, 1, 0, 0, 0, 0].</p><p><strong>The model</strong></p><p>One of the goals of this project was to implement a system with 2 models and I chose to use an auto-encoder as my first model and a basic classifier for my second as illustrate below.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2019/10/image-7.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The Models" loading="lazy"><figcaption>2 model structure</figcaption></figure><p>Keras is used to simplify development and training, config files are used to store hyperparameters and file paths and I developer a basic helper for loading MNIST image data as I am not using Keras for data loading.</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Supporting python objects</th>
<th style="text-align:center">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/adidinchuk/tf-mnist-project/blob/master/src/autoencoder/config.py">Config</a></td>
<td style="text-align:center">Configuration file for the training</td>
</tr>
<tr>
<td><a href="https://github.com/adidinchuk/tf-mnist-project/blob/master/src/libs/data.py">MNISTProcessor</a></td>
<td style="text-align:center">MNIST data loader</td>
</tr>
<tr>
<td><a href="https://github.com/adidinchuk/tf-mnist-project/blob/master/src/libs/data.py">DataWrapper</a></td>
<td style="text-align:center">Object to handle training and testing data</td>
</tr>
<tr>
<td><a href="https://">Visualizer</a></td>
<td style="text-align:center">Stored functions to help visualize results</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p><u><em>The Auto-encoder:</em></u> There is a lot of great material on the auto-encoder network online including the wiki entry <strong><a href="https://en.wikipedia.org/wiki/Autoencoder">here</a></strong>. In a nutshell an auto-encoder is an unsupervised symmetrical neural network that compresses the feature vector into significantly fewer dimensions. The network is trained by using features as both the input and output of the network, teaching the filters to compress the features. One of the key uses of the auto-encoder is noise reduction and this is what it will be used for here.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-3.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The Models" loading="lazy"><figcaption>Typical auto-encoder</figcaption></figure><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Parameters</th>
<th style="text-align:center"></th>
</tr>
</thead>
<tbody>
<tr>
<td>Graph</td>
<td style="text-align:center">728-152-76-38-4-38-76-152-728</td>
</tr>
<tr>
<td>Activation</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/activations/tanh">Tanh for all layers</a></td>
</tr>
<tr>
<td>Loss Function</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/losses/MSE">Mean squared error</a></td>
</tr>
<tr>
<td>Optimizer</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adadelta">Adadelta</a> initial learning rate 1.0</td>
</tr>
<tr>
<td>Batch Size</td>
<td style="text-align:center">50</td>
</tr>
<tr>
<td>Epochs</td>
<td style="text-align:center">500</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Using Keras we can implement the neural network using the following code.</p><!--kg-card-begin: markdown--><pre><code class="language-python">from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.compat.v1 import flags
from tensorflow.keras import optimizers
import sys, os
import config as conf

#set up and parse custom flags
flags.DEFINE_integer(&apos;model_version&apos;, conf.version, &quot;Width of the image&quot;)
flags.DEFINE_boolean(&apos;rebuild&apos;, False, &quot;Drop the checkpoint weights and rebuild model     from scratch&quot;)
flags.DEFINE_string(&apos;lib_folder&apos;, conf.lib_folder, &quot;Local library folder&quot;)
FLAGS = flags.FLAGS

#mount the library folder
sys.path.append(os.path.abspath(FLAGS.lib_folder))
from data import MNISTProcessor
import visualizer as v

#load data
data_processor = MNISTProcessor(conf.data_path, conf.train_labels, 
                            conf.train_images, &apos;&apos;, &apos;&apos;)                               
x_data_train, y_data_train = data_processor.load_train(normalize=True).get_training_data()

#initialize the network
input_layer = Input(shape=(784,), name=&apos;input&apos;)
network = Dense(152, activation=&apos;tanh&apos;, name=&apos;dense_1&apos;)(input_layer)
network = Dense(76, activation=&apos;tanh&apos;, name=&apos;dense_2&apos;)(network)
network = Dense(38, activation=&apos;tanh&apos;, name=&apos;dense_3&apos;)(network)
network = Dense(4, activation=&apos;tanh&apos;, name=&apos;dense_4&apos;)(network)
network = Dense(38, activation=&apos;tanh&apos;, name=&apos;dense_5&apos;)(network)
network = Dense(76, activation=&apos;tanh&apos;, name=&apos;dense_6&apos;)(network)
network = Dense(152, activation=&apos;tanh&apos;, name=&apos;dense_7&apos;)(network)
output = Dense(784, activation=&apos;tanh&apos;, name=&apos;output&apos;)(network)

autoencoder = Model(inputs=input_layer, outputs=output, name=&apos;autoencoder&apos;)
autoencoder.compile(optimizer=optimizers.Adadelta(learning_rate=1.0), loss=&apos;MSE&apos;,     metrics=[&apos;accuracy&apos;])

# Create a callback that saves the model&apos;s weights
cp_callback = ModelCheckpoint(filepath=conf.checkpoint_path, save_weights_only=True, verbose=1)

#load an existing model to continue training
if(not FLAGS.rebuild):
    try:
        autoencoder.load_weights(conf.checkpoint_path)
    except:
        print(&apos;No checkpoint found, building filters from scratch.&apos;)

#run the training
autoencoder.fit(x_data_train, x_data_train,
            epochs=conf.epochs,
            batch_size=conf.batch_size,
            shuffle=True,
            callbacks=[cp_callback])

#save the production version of the model
try:
    os.mkdir(conf.final_model_path + &apos;/&apos; + str(FLAGS.model_version))
except OSError:
    print (&quot;Creation of the directory %s failed&quot; % conf.final_model_path + &apos;/&apos; +     str(FLAGS.model_version))

autoencoder.save(conf.final_model_path + &apos;/&apos; + str(FLAGS.model_version),     overwrite=True, save_format=&apos;tf&apos;) 

autoencoder.summary()   

# run a sample for visualization
clean_images = autoencoder.predict(x_data_train)
v.visualize_autoencoding(x_data_train, clean_images, digits_to_show=10) 
</code></pre>
<blockquote>
<p>To break down the code a little<br>
lines 10-13 - using tensorflow flags to pull command line argument values<br>
lines 21-23 - process the MNIST data set into features and labels<br>
lines 26-37 - set up the neural network strcture and optimizer<br>
lines 40    - set up callback for saving checkpoints during training<br>
lines 43-47 - load any existing checkpoints<br>
lines 50-54 - train the model<br>
lines 57-62 - save a production version that will be ready for serving<br>
lines 64-68 - display final model strcture and some sample autoencodings</p>
</blockquote>
<!--kg-card-end: markdown--><p>The following function was created to help visualize the auto-encoder result.</p><!--kg-card-begin: markdown--><pre><code>import matplotlib.pyplot as plt


def visualize_autoencoding(original_data, decoded_data, digits_to_show=10):
    plt.figure(figsize=(20, 4))
    for i in range(digits_to_show):
        # display original
        sub_plot = plt.subplot(2, digits_to_show, i + 1)
        plt.imshow(original_data[i].reshape(28, 28))
        plt.gray()
        sub_plot.get_xaxis().set_visible(False)
        sub_plot.get_yaxis().set_visible(False)

        # display reconstruction
        sub_plot = plt.subplot(2, digits_to_show, i + 1 + digits_to_show)
        plt.imshow(decoded_data[i].reshape(28, 28))
        plt.gray()
        sub_plot.get_xaxis().set_visible(False)
        sub_plot.get_yaxis().set_visible(False)
    plt.show()
</code></pre>
<blockquote>
<p>this function can be called like so: visualize_autoencoding(x_data_train, clean_images, digits_to_show=4) from our training program after the training is completed.</p>
</blockquote>
<!--kg-card-end: markdown--><p>After training my error loss was around 0.025 and you can see below what a few sample images looked like after being passed through the trained auto-encoder. The result could be improved but this should be satisfactory for our needs.</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-13.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The Models" loading="lazy"></figure><p><em><u>The Classifier:</u></em> The second model will take the 784 dimensional vector output by the auto-encoder and and classifying the data into one of the 10 possible digit values [0, 9]. A simple <a href="http://mathworld.wolfram.com/HyperbolicTangent.html"><strong>tanh </strong></a>activated deep neural network will be used.</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Parameters</th>
<th style="text-align:center"></th>
</tr>
</thead>
<tbody>
<tr>
<td>Graph</td>
<td style="text-align:center">784-140-80-40-10</td>
</tr>
<tr>
<td>Activation</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/activations/tanh">Tanh for all layers</a></td>
</tr>
<tr>
<td>Loss Function</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/losses/MSE">Mean squared error</a></td>
</tr>
<tr>
<td>Optimizer</td>
<td style="text-align:center"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adadelta">Adadelta</a> initial learning rate 1.0</td>
</tr>
<tr>
<td>Batch Size</td>
<td style="text-align:center">50</td>
</tr>
<tr>
<td>Epochs</td>
<td style="text-align:center">100</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Keras was used to implement the classifier as well. We first load and process the image data through the auto-encoder before using as the feature input for training of the classifier.</p><!--kg-card-begin: markdown--><pre><code>from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
from tensorflow.keras import optimizers
from tensorflow.compat.v1 import flags
import tensorflow.keras as Keras
from tensorflow.keras.callbacks import ModelCheckpoint
import sys, os
import config as conf    

#set up and parse custom flags
flags.DEFINE_integer(&apos;model_version&apos;, conf.version, &quot;Width of the image&quot;)
flags.DEFINE_boolean(&apos;rebuild&apos;, False, &quot;Drop the checkpoint weights and rebuild model from scratch&quot;)
flags.DEFINE_string(&apos;lib_folder&apos;, conf.lib_folder, &quot;Local library folder&quot;)
flags.DEFINE_integer(&apos;encoder_version&apos;, 1, &quot;Autoencoder version to use&quot;)
FLAGS = flags.FLAGS

#mount the library folder
sys.path.append(os.path.abspath(FLAGS.lib_folder))
from data import MNISTProcessor

#load data
data_processor = MNISTProcessor(conf.data_path, conf.train_labels, 
                            conf.train_images, &apos;&apos;, &apos;&apos;)
x_data_train, y_data_train = data_processor.load_train(normalize=True).get_training_data()

# Load the autoencoder model, including its weights and then process images
autoencoder = Keras.models.load_model(conf.autoencoder_model_path + &apos;/&apos; +  str(FLAGS.encoder_version))
clean_images = autoencoder.predict(x_data_train)

#initialize the classification network
input_layer = Input(shape=(784,))
network = Dense(140, activation=&apos;tanh&apos;, name=&apos;dense_1&apos;)(input_layer)
network = Dense(80, activation=&apos;tanh&apos;, name=&apos;dense_2&apos;)(network)
network = Dense(40, activation=&apos;tanh&apos;, name=&apos;dense_3&apos;)(network)
output = Dense(10, activation=&apos;tanh&apos;, name=&apos;dense_4&apos;)(network)

classifier = Model(inputs=input_layer, outputs=output, name=&apos;classifier&apos;)
classifier.compile(optimizer=optimizers.Adadelta(learning_rate=1.0), loss=&apos;MSE&apos;, metrics=[&apos;accuracy&apos;])

# Create a callback that saves the model&apos;s weights
cp_callback = ModelCheckpoint(filepath=conf.checkpoint_path, save_weights_only=True, verbose=1)

#load an existing model to continue training
if(not FLAGS.rebuild):
    try:
        classifier.load_weights(conf.checkpoint_path)
    except:
        print(&apos;No checkpoint found, building filters from scratch.&apos;)

#run the model
classifier.fit(clean_images, y_data_train,
            epochs=conf.epochs,
            batch_size=conf.batch_size,
            shuffle=True,
            callbacks=[cp_callback])

#save the production version of the model
try:
    os.mkdir(conf.final_model_path + &apos;/&apos; + str(FLAGS.model_version))
except OSError:
    print (&quot;Creation of the directory %s failed&quot; % conf.final_model_path + &apos;/&apos; + str(FLAGS.model_version))

classifier.save(conf.final_model_path + &apos;/&apos; + str(FLAGS.model_version), overwrite=True, save_format=&apos;tf&apos;) 

classifier.summary()     
</code></pre>
<blockquote>
<p>To break down the code a little<br>
lines 11-15 - using tensorflow flags to pull command line argument values<br>
lines 22-24 - process the MNIST data set into features and labels<br>
lines 27-28 - load the autoencoder model and process the feature data set<br>
lines 31-38 - set up the neural network strcture and optimizer<br>
lines 41    - set up callback for saving checkpoints during training<br>
lines 45-49 - load any existing checkpoints<br>
lines 51-55 - train the model<br>
lines 57-62 - save a production version that will be ready for serving<br>
lines 65    - display final model strcture</p>
</blockquote>
<!--kg-card-end: markdown--><p><strong>Lessons learned</strong></p><p><em><u>Poor initial model convergence</u> - </em>I wrote my initial code using the stand alone keras library, however due to challenges of saving the models in a servable format I had to switch the the tf.keras library instead. After my switch my models flat out refused to converge during training. After many hours of debugging I discovered that the <code>keras.optimizers.Adadelta</code> optimizer uses a default starting learning rate of 1.0, where as the tf.keras.optimizersAdadelta optimizer initializes with a learning rate of 0.001. Forcing the learning rate addressed this issue for me and you can see this reflected in my code. </p><p><strong>For the lazy</strong></p><p>My results can be reproduced with the following commands:</p><!--kg-card-begin: markdown--><pre><code>#navigate to where you would like to generate the repository
$ git clone https://github.com/adidinchuk/tf-mnist-project
$ cd tf-mnist-project

$ curl http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz --output data/train-images-idx3-ubyte.gz
$ curl http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz --output data/train-labels-idx1-ubyte.gz

#Unzip the data using your prefer compression tool.
#Make sure the file names and location do not change,
#otherwise you will have to make the appropriate changes in the config filesv

#once the data has been extracted train the autoencoder model using
$ py -3.6 src/autoencoder/graph.py --model_version 1

#after the training completes you should see a .pb model file in the models/autoencoder/production folder

#now run the classifier training
$ py -3.6 src/classifier/graph.py  --model_version 1 --encoder_version 1

#after the training completes you should see a .pb model file in the models/classifier/production/#/ folder
</code></pre>
<!--kg-card-end: markdown--><p>You should now see the production models under <code>models/autoencoder/production/1</code> and <code>models/classifier/production/1</code> that looks like this:</p><figure class="kg-card kg-image-card"><img src="http://www.theappliedarchitect.com/content/images/2019/11/image-19.png" class="kg-image" alt="TensorFlow + Docker MNIST Classifier - The Models" loading="lazy"></figure><p>The entire TensorFlow github repository along with complete instructions on running the model can be found <a href="https://github.com/adidinchuk/tf-mnist-project"><strong>here</strong></a>. Now that we have both the auto-encoder and classifier models generated we can take a look at deploying them via TensorFlow serving, which I will do in my next post.</p><p><strong>Here is a summary of the components involved in this project:</strong></p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Section</th>
<th style="text-align:center">Git Repository</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-implementation/">Introduction</a></td>
<td style="text-align:center">N/A</td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-project/">The Models</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-mnist-project">tf-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-serving-models/">Serving Models</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/tf-serving-mnist-project">tf-serving-mnist-project</a></td>
</tr>
<tr>
<td><a href="http://www.theappliedarchitect.com/tensorflow-docker-mnist-classifier-the-user-interface/">The User Interface</a></td>
<td style="text-align:center"><a href="https://github.com/adidinchuk/angular-mnist-project">angular-mnist-project</a></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>