<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Dmitry Salahutdinov</title>
    <description>The latest articles on Forem by Dmitry Salahutdinov (@dsalahutdinov).</description>
    <link>https://forem.com/dsalahutdinov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F73698%2F950db56d-d2f1-4ab0-9dff-ea8f6d68292f.jpeg</url>
      <title>Forem: Dmitry Salahutdinov</title>
      <link>https://forem.com/dsalahutdinov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dsalahutdinov"/>
    <language>en</language>
    <item>
      <title>Setup web-page screenshots within a minute</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 06 Apr 2020 09:40:43 +0000</pubDate>
      <link>https://forem.com/amplifr/setup-web-page-screenshots-within-a-minute-53ba</link>
      <guid>https://forem.com/amplifr/setup-web-page-screenshots-within-a-minute-53ba</guid>
      <description>&lt;p&gt;For many years &lt;a href="https://phantomjs.org/" rel="noopener noreferrer"&gt;PhantomJS&lt;/a&gt; is used to generate a web-page screenshot.  As we have a Ruby web-application on Kubebernetes, it brought some inconveniences: we had to install NodeJs and PhantomJS rights into the main Ruby Docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Phantomjs is installed via npm to avoid HUGE dependencies such as QT and others.
RUN npm install phantomjs-prebuilt \
  &amp;amp;&amp;amp; ln -s /node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs /usr/local/bin/phantomjs \
  &amp;amp;&amp;amp; rm -rf /tmp/phantomjs

COPY ./fonts/Helvetica.ttf /usr/share/fonts/truetype/Helvetica.ttf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It causes all the application instances, like web, sidekiq, and others, had these low-level dependencies installed even if they did not need to generate screenshots.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://semaphoreci.com/blog/2018/03/27/phantomjs-is-dead-use-chrome-headless-in-continuous-integration.html" rel="noopener noreferrer"&gt;For some time&lt;/a&gt; PhantomJS are not supported anymore. The new alternative is to use Google Chrome within the "headless" mode, a great example of doing it from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-chrome --headless --window-size=1180,768 --screenshot=/tmp/screenshot.png https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to get google-chrome installed to a machine, so using this command rights from the ruby code doesn't change anything fundamentally.&lt;/p&gt;

&lt;p&gt;That is why I decided to extract it into separated HTTP based micro-service for capturing web-page and returning an image.&lt;/p&gt;

&lt;p&gt;Here it is &lt;a href="https://github.com/dsalahutdinov/screenshot" rel="noopener noreferrer"&gt;https://github.com/dsalahutdinov/screenshot&lt;/a&gt;&lt;br&gt;
It could be touched with &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl --output screenshot.png http://screenshot.local/screenshot\?width\=1180\&amp;amp;height\=768\&amp;amp;url\=https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with browser as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpghlk41nmkn7ftl07h4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpghlk41nmkn7ftl07h4v.png" alt="Generated screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Internals
&lt;/h2&gt;

&lt;p&gt;Internally it is simple HTTP-wrapper for running &lt;code&gt;Chrome&lt;/code&gt; and return resulting image file. Nothing more on it. Screenshot tool is Kubernetes-ready, and you can get it up in a minute by running &lt;code&gt;kubectl&lt;/code&gt; or using predefined &lt;code&gt;helm&lt;/code&gt;-chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Helm Chart
&lt;/h2&gt;

&lt;p&gt;The predefined helm-chart is located &lt;a href="https://github.com/dsalahutdinov/screenshot-helm" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This repo also works as a helm-repository on top of Github pages. Add the repository to your helm settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm repo add screenshot-helm https://dsalahutdinov.github.io/screenshot-helm/
&lt;span class="s2"&gt;"screenshot-helm"&lt;/span&gt; has been added to your repositories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy the chart into current Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;screenshot-helm/screenshot &lt;span class="nt"&gt;--name&lt;/span&gt; screenshot &lt;span class="nt"&gt;--namespace&lt;/span&gt; screenshot

NAME:   screenshot
LAST DEPLOYED: Sat Apr  4 16:59:02 2020
NAMESPACE: screenshot
STATUS: DEPLOYED

RESOURCES:
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Deployment
NAME         AGE
screenshot  1s

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Pod&lt;span class="o"&gt;(&lt;/span&gt;related&lt;span class="o"&gt;)&lt;/span&gt;
NAME                      AGE
screenshot-76948b-rrq77  1s

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Service
NAME         AGE
screenshot  1s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since then, service will run as "internal" without any ingress resources. You can access it within the cluster by its internal DNS name, [release].[namespace].kubernetes.local, or with just short name [release].[namespace]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--output&lt;/span&gt; screenshot.png &lt;span class="se"&gt;\&lt;/span&gt;
     http://screenshot.screenshot/screenshot/?width&lt;span class="o"&gt;=&lt;/span&gt;1180&amp;amp;height&lt;span class="o"&gt;=&lt;/span&gt;768&amp;amp;url&lt;span class="o"&gt;=&lt;/span&gt;https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run on Minikube
&lt;/h2&gt;

&lt;p&gt;Here is the manual for running screenshot service into &lt;a href="https://github.com/kubernetes/minikube" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt; local cluster with using "external" domain name &lt;code&gt;screenshot.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# starts minikube
minikube start

# gets minikube ip address and adds it as screenshot.local domain address
echo "$(minikube ip) screenshot.local" | sudo tee -a /etc/hosts

# adds helm repository
$ helm repo add screenshot-helm 

# installs screenshot service with the enabled ingress
$ helm install screenshot-helm/screenshot \
  --name screenshot \
  --namespace screenshot \
  --set ingress.enabled=True \
  --set ingress.hosts\[0\].host=screenshot.local \
  --set ingress.hosts\[0\].paths\[0\]=/screenshot

NAME:   screenshot
LAST DEPLOYED: Mon Apr  6 10:51:54 2020
NAMESPACE: screenshot
STATUS: DEPLOYED

RESOURCES:
==&amp;gt; v1/Deployment
NAME        AGE
screenshot  0s

==&amp;gt; v1/Pod(related)
NAME                         AGE
screenshot-58dd688598-ml9nw  0s

==&amp;gt; v1/Service
NAME        AGE
screenshot  0s

==&amp;gt; v1/ServiceAccount
NAME        AGE
screenshot  0s

==&amp;gt; v1beta1/Ingress
NAME        AGE
screenshot  0s


NOTES:
1. Get the application URL by running these commands:
  http://screenshot.local/screenshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the start, service will be available to access with &lt;code&gt;amplifr.local&lt;/code&gt; domain. Other supported options of helm-chart, like resources, tls, affinity you can find &lt;a href="https://github.com/dsalahutdinov/screenshot-helm/blob/master/values.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Using ruby is very straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;height: &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s1"&gt;'https://amplifr.com/en'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'http://screenshot.screenshot/screenshot?'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;

&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'screenshot.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;screenshot&lt;/code&gt; tool is straightforward and solves the simple, practical task - web-page screenshot generation. You can run into a Kubernetes cluster in a minute if you would need a web-page screenshot generation one day.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ruby</category>
      <category>go</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Monitoring external services with Prometheus and Grafana</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 23 Mar 2020 05:07:21 +0000</pubDate>
      <link>https://forem.com/amplifr/monitoring-external-services-with-prometheus-and-grafana-5eh6</link>
      <guid>https://forem.com/amplifr/monitoring-external-services-with-prometheus-and-grafana-5eh6</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Time ago, web-applications depended only on self-written code or libraries. It was easy to diagnose any problem, plainly investigating how an application work.&lt;/p&gt;

&lt;p&gt;Today, modern web-applications tend to have many external dependencies, such as microservices or external SaaS. It makes your system depend not only on the internal factors but on the reliability of external services as well. When something fails, it's essential to localize a problem and find out which side caused it.&lt;/p&gt;

&lt;p&gt;That is why monitoring of external services call is crucial.&lt;/p&gt;

&lt;p&gt;I work for &lt;a href="https://amplifr.com" rel="noopener noreferrer"&gt;Amplifr&lt;/a&gt;, the social media automation platform. Our Ruby core application depends on many external APIs: Facebook, Instagram, LinkedIn, Pinterest, &lt;a href="https://bitly.com" rel="noopener noreferrer"&gt;Bitly&lt;/a&gt;, Stripe, Amplitude, Intercom, and many others. &lt;br&gt;
We also are integrated with many self-hosted micro-services, such as &lt;a href="https://github.com/imgproxy/imgproxy" rel="noopener noreferrer"&gt;imgproxy&lt;/a&gt; for resizing images, &lt;a href="https://github.com/dsalahutdinov/screenshot" rel="noopener noreferrer"&gt;browser screenshot generator&lt;/a&gt;, social services bots, and other internal domain-specific services.&lt;/p&gt;

&lt;p&gt;When an application has so many integrations, it's handy to have a quick  traffic details overview:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many requests application does&lt;/li&gt;
&lt;li&gt;how much time consumes an average request&lt;/li&gt;
&lt;li&gt;how many of the attempts fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was my second point to set monitoring up and write my own solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics
&lt;/h2&gt;

&lt;p&gt;As most APIs work over HTTP, we will monitor the HTTP requests.&lt;/p&gt;

&lt;p&gt;Meet my gem &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests" rel="noopener noreferrer"&gt;yabeda-http_requests&lt;/a&gt;. It is the new one of the &lt;a href="https://github.com/yabeda-rb" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt; monitoring solution by &lt;a href="https://evilmartians.com/" rel="noopener noreferrer"&gt;Evil Martians&lt;/a&gt;. If you are not familiar with it, here is the &lt;a href="https://evilmartians.com/chronicles/meet-yabeda-modular-framework-for-instrumenting-ruby-applications" rel="noopener noreferrer"&gt;great article&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;In a nutshell, Yabeda - is the open-source Ruby framework for collection metrics of Ruby processes (Rack/Rails, Sidekiq,p Puma) and exporting them into Prometheus (or any other adapter).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yabeda-http_requiests&lt;/code&gt; wraps all the external HTTP requests with the help of &lt;a href="https://github.com/aderyabin/sniffer" rel="noopener noreferrer"&gt;sniffer gem&lt;/a&gt;. Sniffer patches &lt;a href="https://github.com/aderyabin/sniffer/tree/master/lib/sniffer/adapters" rel="noopener noreferrer"&gt;all popular HTTP libraries&lt;/a&gt; and logs performing requests. By using middleware, &lt;code&gt;yabeda-http_requests&lt;/code&gt; gem calculates and stores metrics. Then metrics could be exported with &lt;code&gt;yabeda-prometheus&lt;/code&gt; gem.&lt;/p&gt;

&lt;p&gt;To get started with it you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to add the gem into your dependencies (Gemfile)&lt;/li&gt;
&lt;li&gt;set up metrics exporter for Prometheus&lt;/li&gt;
&lt;li&gt;tune up Prometheus to scrape the metrics endpoint&lt;/li&gt;
&lt;li&gt;import predefined &lt;a href="https://grafana.com/grafana/dashboards/11959" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; into your Grafana config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could find all the details &lt;code&gt;yabeda-http_requests&lt;/code&gt;  &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests#usage" rel="noopener noreferrer"&gt;at the README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's have a look at providing metrics &amp;amp; charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to monitor?
&lt;/h2&gt;

&lt;p&gt;The first release of &lt;code&gt;yabeda-http_requests&lt;/code&gt; includes some basics:&lt;/p&gt;

&lt;h3&gt;
  
  
  RPM
&lt;/h3&gt;

&lt;p&gt;The basic metric is RPM (request per minute). It shows how many HTTP request application does.&lt;/p&gt;

&lt;p&gt;Formally, &lt;code&gt;yabeda-http_requests&lt;/code&gt; collect a &lt;code&gt;http_request_count&lt;/code&gt; as counter. It is separated by quest host, port and method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# HELP http_response_total A counter of the total number
# of external HTTP responses.
http_response_total{host="twitter.com",port="443",method="GET",status="200"} 131.0
http_response_total{host="dev.to",port="443",method="GET",status="200"} 131.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Than, using &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#rate" rel="noopener noreferrer"&gt;rate&lt;/a&gt; function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sum by (host) (rate(http_response_total[1m]) )


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;renders the RPM chart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3j9smoqyxk04bel6043.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3j9smoqyxk04bel6043.png" alt="Request per minute (by host)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is easy to regroup the same chart by status:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9hy5732zxlchmuahfqr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9hy5732zxlchmuahfqr2.png" alt="Request duration chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗ "RPM by status" chart uses another metric, which is called &lt;code&gt;http_response_total&lt;/code&gt; which logs the HTTP responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request duration
&lt;/h3&gt;

&lt;p&gt;When you want to investigate which one of the dependent components causes a slow-down, there is a histogram duration data and the corresponding chart. It shows how many times consumes the slowest request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd6ixo7quxvib6lewblb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd6ixo7quxvib6lewblb3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Application
&lt;/h2&gt;

&lt;p&gt;This section is for practical approach lovers. Here is the &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests/tree/master/example" rel="noopener noreferrer"&gt;example of web-application&lt;/a&gt;. It shows how all things work together: web-application, metrics collector, exporter, and Prometheus &amp;amp; Grafana.&lt;/p&gt;

&lt;p&gt;The example has a docker-compose config for automatic setup. Just run the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/yabeda-rb/yabeda-http_requests
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;yabeda-http_requests/example
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command runs the rack-application, Sidekiq, Prometheus, and Grafana.&lt;br&gt;
Rack application adds random web scrapping jobs to the sidekiq. Navigate to &lt;code&gt;http://localhost:9292&lt;/code&gt; to enqueue some jobs.&lt;/p&gt;

&lt;p&gt;"Sidekiq instance" runs background scrapping tasks, collects metrics, and exposes an endpoint with data for the Prometheus. &lt;br&gt;
Here is the basic sidekiq configuration, showing how the sidekiq process run its web-server:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# sidekiq config&lt;/span&gt;
&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_server&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# configures all the Yabeda metrics&lt;/span&gt;
  &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Yabeda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure!&lt;/span&gt;

  &lt;span class="c1"&gt;# expose 9394 port with /metrics path for prometheus&lt;/span&gt;
  &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Yabeda&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Prometheus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_metrics_server!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Navigate to &lt;code&gt;http://localhost:9394/metrics&lt;/code&gt; to see metrics endpoint, and you will see the metrics values formatted as text:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# TYPE http_request_total counter
# HELP http_request_total A counter of the total number of external HTTP requests.
http_request_total{host="twitter.com",port="443",method="GET"} 16922.0
http_request_total{host="dev.to",port="443",method="GET"} 17083.0
http_request_total{host="amplifr.com",port="443",method="GET"} 451.0
http_request_total{host="www.sitepoint.com",port="443",method="GET"} 428.0
# TYPE http_response_total counter
# HELP http_response_total A counter of the total number of external HTTP responses.
http_response_total{host="twitter.com",port="443",method="GET",status="200"} 16910.0
http_response_total{host="dev.to",port="443",method="GET",status="200"} 17080.0
http_response_total{host="amplifr.com",port="443",method="GET",status="301"} 451.0
http_response_total{host="www.sitepoint.com",port="443",method="GET",status="200"} 428.0
http_response_total{host="twitter.com",port="443",method="GET",status="503"} 1.0
# TYPE http_response_duration_milliseconds histogram
# HELP http_response_duration_milliseconds A histogram of the response                                                duration (milliseconds).
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="0.5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="1"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="2.5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="10"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="25"} 0.0
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Prometheus scrapes &lt;code&gt;http://sidekiq:9394/metrics&lt;/code&gt; periodically and stores the metrics into its database.&lt;/p&gt;

&lt;p&gt;Here is the overview diagram of how it works together:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyykdbbpezp7ivllfy88j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyykdbbpezp7ivllfy88j.png" alt="HTTP requests monitoring example diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grafana visualizes the data. Navigate to &lt;code&gt;http://localhost:3000/d/OGd-oEXWz/yabeda-external-http-requests&lt;/code&gt;. Use &lt;code&gt;admin/foobar&lt;/code&gt; to login and see the template dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyw2vtcaiw8x9myke9h24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyw2vtcaiw8x9myke9h24.png" alt="Yabeda External HTTP requests Grafana dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗You could import prepared Grafana dashboard  &lt;a href="https://grafana.com/grafana/dashboards/11959" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The monitoring of external HTTP calls is essential. It is handy to an overview of your outgoing application traffic. It also helps to diagnose slow-down issues of dependent services.&lt;/p&gt;

&lt;p&gt;Yabeda framework and the &lt;code&gt;yabeda-http_requests&lt;/code&gt; let to quickly set up metrics and visualize them using a predefined Grafana dashboard template.&lt;/p&gt;

&lt;p&gt;If you have any other ideas, please do not hesitate to contribute: by writing an Issue, sending PR, or leaving the comment.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>monitoring</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Kubernetes-native development: sync files with Ksync</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 27 Jan 2020 20:42:50 +0000</pubDate>
      <link>https://forem.com/dsalahutdinov/kubernetes-native-development-sync-files-between-a-local-machine-and-remote-pod-2i0i</link>
      <guid>https://forem.com/dsalahutdinov/kubernetes-native-development-sync-files-between-a-local-machine-and-remote-pod-2i0i</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/amplifr/continuous-deployment-to-minikube-with-skaffold-18ib"&gt;Previous post&lt;/a&gt; I started to set up continuous deployment for the development process. &lt;a href="https://skaffold.dev"&gt;Skaffold&lt;/a&gt; is a handy tool for doing that.&lt;/p&gt;

&lt;p&gt;Let's go deeper this time and imagine the next steps for making up a  &lt;strong&gt;full-featured "local" development environment with Kubernetes&lt;/strong&gt;. Running ruby code outside of the local machine keeps it clear and doesn't impose any resource limits.&lt;/p&gt;

&lt;p&gt;It is the essence of the idea "Kubernetes-native development": to run all the code at a remote or local Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;The issue I try to investigate here is file synchronization. If we edit code locally and run it somewhere remotely, we could get sources out of sync. Unequal sources could cause unexpected results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/amplifr/dockerize-the-multi-services-application-for-local-development-2oig"&gt;For local development with Docker&lt;/a&gt; it is common to use Docker volumes. The same idea could be applied here in terms of &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/"&gt;Kubernetes Persistent Volumes&lt;/a&gt;. I failed to use persistent volume, mapping them to the Minikube host machine. And map to local Mac directory onto the Minikube host machine. &lt;br&gt;
Even if it worked well, there are other issues with that approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a cluster could be remote. It's a big deal to map the local directory into the remote cluster host machine&lt;/li&gt;
&lt;li&gt;having additional persistent volume manifests only for developer's needs is obsessive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I decided to synchronize files instead and try out the &lt;code&gt;ksync&lt;/code&gt; tool for Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ksync/ksync"&gt;Ksync&lt;/a&gt; speeds up developers who build applications for Kubernetes. It transparently updates containers running on the cluster from your local checkout. This enables developers to use their favorite IDEs, such as Atom or Sublime Text to work from inside a cluster instead of from outside it. There is no reason to wait minutes to test code changes when you can see the results in seconds.&lt;/p&gt;

&lt;p&gt;If want to do something like &lt;code&gt;docker run -v /foo:/bar&lt;/code&gt; with Kubernetes, &lt;code&gt;ksync&lt;/code&gt; is for you!&lt;/p&gt;

&lt;p&gt;Let's set up file sync for already known &lt;a href="https://github.com/GoogleContainerTools/skaffold/tree/master/examples/ruby"&gt;Skaffold Ruby example&lt;/a&gt;. We'll need the same tooling as in the previous article: &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;minikube&lt;/code&gt;, &lt;code&gt;skaffold&lt;/code&gt;, and installed &lt;code&gt;ksync&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install ksync
&lt;/h2&gt;

&lt;p&gt;Find the installation details &lt;a href="https://github.com/ksync/ksync#installation"&gt;here&lt;/a&gt;, or just run for MacOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl https://ksync.github.io/gimme-that/gimme.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup ksync
&lt;/h2&gt;

&lt;p&gt;Then we have to initialize ksync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ksync init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command initializes &lt;code&gt;ksync&lt;/code&gt; and installs the server component on your cluster. One part of &lt;code&gt;ksync&lt;/code&gt; works as the entity in the Kubernetes cluster and has to be set up. See more details about &lt;code&gt;ksync&lt;/code&gt; &lt;a href="https://github.com/ksync/ksync/blob/master/docs/architecture.md"&gt;architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out the Ruby example sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:GoogleContainerTools/skaffold.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples/ruby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to enable &lt;code&gt;ksync&lt;/code&gt; file watch with running the &lt;code&gt;watch&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ksync watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable watching changes within &lt;code&gt;($pwd)/backend&lt;/code&gt; folder and sync them back and forth with &lt;code&gt;/app/&lt;/code&gt; directory of running containers:&lt;/p&gt;

&lt;p&gt;We will use the specific label &lt;code&gt;app.kubernetes.io/ksync: 'true'&lt;/code&gt; presence to determine the scope of pod container to track changes with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ksync create &lt;span class="nt"&gt;--selector&lt;/span&gt; app.kubernetes.io/ksync&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--reload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/backend /app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ruby app sample
&lt;/h2&gt;

&lt;p&gt;Change the &lt;code&gt;k8s/deployment.yaml&lt;/code&gt; to add the specific tag &lt;code&gt;app.kubernetes.io/ksync: 'true'&lt;/code&gt; to pod's containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9292&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9292&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
&lt;span class="s"&gt;--------&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
        &lt;span class="s"&gt;app.kubernetes.io/ksync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby-example&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9292&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RACK_ENV&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;skaffold run &lt;span class="nt"&gt;--tail&lt;/span&gt;
Generating tags...
 - ruby-example -&amp;gt; ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Found Locally
Tags used &lt;span class="k"&gt;in &lt;/span&gt;deployment:
 - ruby-example -&amp;gt; ruby-example:de8f4f77b01e508f5d862a9ef84d9f33a50d03a88225b0e1b26ddb04d948cded
   &lt;span class="nb"&gt;local &lt;/span&gt;images can&lt;span class="s1"&gt;'t be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - service/ruby created
 - deployment.apps/ruby created
[ruby-66d8d5758-g6m9r ruby] Puma starting in single mode...
[ruby-66d8d5758-g6m9r ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-66d8d5758-g6m9r ruby] * Min threads: 0, max threads: 16
[ruby-66d8d5758-g6m9r ruby] * Environment: development
[ruby-66d8d5758-g6m9r ruby] * Listening on tcp://0.0.0.0:9292
[ruby-66d8d5758-g6m9r ruby] Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we won't run &lt;code&gt;skaffold&lt;/code&gt; in development mode to prevent the doubled file sync by &lt;code&gt;ksync&lt;/code&gt; and &lt;code&gt;skaffold&lt;/code&gt; concurrently.&lt;/p&gt;

&lt;h2&gt;
  
  
  File sync in action
&lt;/h2&gt;

&lt;p&gt;Any changes made on local files are automatically synchronized with remote containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"gem 'oj'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; backend/Gemfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The similar &lt;code&gt;ksync watch&lt;/code&gt; output means that file is synchronized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;INFO[0008] folder &lt;span class="nb"&gt;sync &lt;/span&gt;running                           &lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-66d8d5758-g6m9r &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0012] updating                                      &lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-66d8d5758-g6m9r &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0012] update &lt;span class="nb"&gt;complete                               &lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-66d8d5758-g6m9r &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0012] updating                                      &lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-console-69674c499f-97m79 &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0012] update &lt;span class="nb"&gt;complete                               &lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-console-69674c499f-97m79 &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0080] updating                                      &lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-console-69674c499f-97m79 &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0080] updating                                      &lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-66d8d5758-g6m9r &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0080] update &lt;span class="nb"&gt;complete                               &lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-66d8d5758-g6m9r &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
INFO[0080] update &lt;span class="nb"&gt;complete                               &lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ruby-console-69674c499f-97m79 &lt;span class="nv"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;massive-koala
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let check the container's &lt;code&gt;Gemfile&lt;/code&gt; to see if changes are applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;--selector&lt;/span&gt; app.kubernetes.io/ksync&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;  &lt;span class="nb"&gt;cat &lt;/span&gt;Gemfile
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;

gem &lt;span class="s1"&gt;'rack'&lt;/span&gt;
gem &lt;span class="s1"&gt;'rack-unreloader'&lt;/span&gt; &lt;span class="c"&gt;# for dynamic reloading&lt;/span&gt;
gem &lt;span class="s1"&gt;'puma'&lt;/span&gt;
gem &lt;span class="s1"&gt;'oj'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we could get inside the pod and run &lt;code&gt;bundle&lt;/code&gt; to update &lt;code&gt;Gemfile.lock&lt;/code&gt; dependencies lock-file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;--selector&lt;/span&gt; app.kubernetes.io/ksync&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; bundle

Using bundler 2.1.2
Using nio4r 2.5.2
Using oj 3.10.1
Using puma 4.3.1
Using rack 2.1.1
Using rack-unreloader 1.7.0
Bundle &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; 4 Gemfile dependencies, 6 gems now installed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;bundle&lt;/code&gt; had finished and updated the lock-file, we got it synchronized back with a local machine!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Perfect, we set up the two-way file synchronization and get it working with &lt;code&gt;ksync&lt;/code&gt;. From now, any source file updates made automatically by ruby-code, get back to a local machine.&lt;/p&gt;

&lt;p&gt;The sync would also work well for generated files, such as &lt;code&gt;rails g migration&lt;/code&gt; or even for initial rails project generation with &lt;code&gt;rails new my_project&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>development</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Ruby conferences to attend in 2020</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Sat, 25 Jan 2020 19:02:46 +0000</pubDate>
      <link>https://forem.com/amplifr/ruby-conferences-to-attend-in-2020-2ckn</link>
      <guid>https://forem.com/amplifr/ruby-conferences-to-attend-in-2020-2ckn</guid>
      <description>&lt;p&gt;Do you think &lt;strong&gt;Ruby&lt;/strong&gt; is dying?&lt;br&gt;
Meanwhile, Rubyists get the sold-out at more than 20 conferences around the world.&lt;/p&gt;

&lt;p&gt;While looking for, I made a list of Ruby conferences you can attend in 2020. Here it is:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://birminghamonrails.com/"&gt;Birmingham On Rails&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; January 31&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Birmingham, AL&lt;/p&gt;

&lt;p&gt;Birmingham on Rails is a forward thinking Ruby on Rails conference with the purpose of building a network of software professionals and educating them on the latest developments and uses of the Rails platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubyfuza.org/"&gt;RubyFuza&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇿🇦South Africa, Cape Town&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyfuza"&gt;@rubyfuza&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rubyfuza is the longest run Ruby conference in South Africa. 2020 will be the 9th event held in Cape Town This year we will be opening the conference up!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyparis.org/"&gt;ParisRB Conf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 18-19&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇫🇷France, Paris&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/parisrb"&gt;@parisrb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meet fellow rubyists from around the world, and expand your Ruby horizons!&lt;/p&gt;

&lt;p&gt;The year 2020 marks the 25th Anniversary of the first release of Ruby and we are lucky (and filled with gratitude) to welcome Ruby creator Yukihiro Matsumoto who will help us open ParisRB with a Keynote speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconf.org.au/2020"&gt;RubyConf AU&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 20-21&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇦🇺Australia, Melbourne&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconf_au"&gt;@rubyconf_au&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a mixture of local and international speakers, there will be two day of talks. We'll cover topics that interest, excite, and delight Rubyists and the Ruby-curious of all levels. There will also be social events hosted where you will get the opportunity to meet like-minded people!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://wrocloverb.com/"&gt;Wroclove.rb&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; March 20-22&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇵🇱Poland, Wroclaw&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/wrocloverb"&gt;@wrocloverb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main goal of the conference is to help Ruby professionals become better at what they do.&lt;/p&gt;

&lt;p&gt;wroclove.rb serves best to people who are already fluent with Ruby. The ideas presented at our conference are very advanced. They are meant to inspire the way we write code, the way we help our clients and users. It’s not uncommon for us to present you ideas that come from the Java and .NET worlds, we believe that their ideas can have a great influence on our community. There are good  reasons, why our conference is called “the best Java conference in the Ruby world”.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://brug.by/"&gt;BRUG.by&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; March 21&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇾Belarus, Minks&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://www.facebook.com/ByRoR/"&gt;@ByRoR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;THE INTERNATIONAL TECH CONFERENCE FOR RUBY ENTHUSIASTS IN MINSK.&lt;/p&gt;

&lt;p&gt;During the conference, we want to focus on the topic of Engineering Happiness. What gets us satisﬁed with our work? What processes, tools, industry news make us happy? What does an engineer need to be happy? What tips and tricks do companies use to increase developers’ happiness? All in all, is happiness really so important?&lt;/p&gt;

&lt;p&gt;If you have something to say on that subject, we are looking forward to your 40-minute presentation (Keynote, PPT or any other) with slides or a live coding session. The QA block will also take 20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyday.it/"&gt;RubyDay&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 2&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇮🇹Verona, Italy&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubydayit"&gt;@rubyday&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyDay 2020 is the 8th edition of the Italian Ruby conference, organized by GrUSP, organizers of events like phpday and jsday. Event is international and all sessions will be in English.&lt;/p&gt;

&lt;p&gt;The event’s goal is to allow all Rubyists to meet and share experience while having fun and networking in an enjoyable context.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubywine.org/"&gt;RubyWine&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 4&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇲🇩Moldova, Chișinău&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyMeditation"&gt;@RubyMeditation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being first of its kind in Moldova, Ruby Wine is intended to promote Ruby programming language among country and fortify existing Ruby community. It will bring together IT professionals for knowledge sharing and let them explore new insights of Ruby's possibilities. This event is a perfect mix of top-notch talks by talented Ruby developers and amazing Moldovan atmosphere.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubywine.org/"&gt;RubyKaigi&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 9-11&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇯🇵Japan, Matsumoto&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubykaigi"&gt;@rubykaigi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyKaigi is an annual conference held in Japan for Ruby programmers that was held for the first time in 2006.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconference.by/"&gt;RubyConfBy&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 18&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇾Belarus, Minks&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyConfBY"&gt;@RubyConfBY&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 6th large technological Ruby, Rails and related technologies conference. Leaders from Eastern+Western Europe and USA will gather to discuss technological aspects of development with Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconfindia.org/"&gt;RubyConfIndia&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 25-26&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇮🇳India, Goa&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconfindia"&gt;@rubyconfindia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf India is a global event complementing other RubyConf events across the world.&lt;/p&gt;

&lt;p&gt;This year our event is a 2 day, single track event focused on Ruby Language, Framework and Tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://railsconf.com/"&gt;RailsConf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 5-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Portland&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/railsconf"&gt;@railsconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RailsConf is the world’s largest gathering of Rails developers, brought together to further discussion and learning about building, managing, and testing Rails applications. With a specific focus on Rails, conference topics can range from new users to administration to advanced techniques.v&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://balkanruby.com/"&gt;BalkanRuby&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 15-26&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇬Bulgaria, Sofia&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/BalkanRuby"&gt;@BalkanRuby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Balkan Ruby's aim is to bring world-class talks to the Balkan area and allow the local community to interact with the wider Ruby community from Europe and around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://spbrubyconf.ru/"&gt;Saint P Ruby Conf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇷🇺Russia, Saint Petersburg&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/saintpruby"&gt;@saintpruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ruby community based in Saint Petersburg, Russia. We do meetups, drinkups and conferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyunconf.eu/"&gt;Ruby Unconf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇩🇪Germany, Hamburg&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyUnconfEU"&gt;@RubyUnconfEU&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What exactly is an Unconference? It is an Barcamp style event. It works like this: Everybody can propose a talk. Everybody votes for the talks they want to see. The most voted talks will be held. You decide what will happen!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://www.reddotrubyconf.com/"&gt;RedDotRubyConference&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June? (is not know yet)&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇸🇬 Singapore&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/reddotrubyconf"&gt;@reddotrubyconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://twitter.com/reddotrubyconf/status/1189344579209908229"&gt;that tweet&lt;/a&gt; the conference will get back this year.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://brightonruby.com/"&gt;BrightonRuby&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; July 3&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇬🇧UK, Brighton&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/brightonruby"&gt;@brightonruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a friendly, inclusive, single day, single track event for Rubyists and the Ruby-curious.&lt;/p&gt;

&lt;p&gt;We’re nice. The speakers are ace.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://rubyconf.nairuby.org/2020"&gt;RubyConf Kenya&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; July 23-25&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇰🇪Kenia, Nairobi&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/NairubyKE"&gt;@NairubyKE&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nairobi Ruby community driven conference.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://euruko2020.org/"&gt;Euruko&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; August 21-22&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇫🇮Finland, Helsinki&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/euruko"&gt;@euruko&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Euruko is a Ruby conference organised annually, each year in a different European city. Join us in Helsinki, Finland on 21st–22nd August 2020 for two days of Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://grillrb.com/"&gt;GrillRB&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; unknown, end of August&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇵🇱Poland, Wroclaw&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/grill_rb"&gt;@grill_rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GrillRB is Polish outdoor single-track community-driven conference fully dedicated to Ruby on Rails framework and Ruby language. During the two day event we are going to discuss the topics which are the most crucial for Ruby community, such as: best practices, development process and the future of Ruby. Come and exchange the knowledge and experience, talk over projects with other Rubyists in relaxing &amp;amp; enjoyable environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyc.eu/"&gt;RubyC&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; September 12-13&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇦Ukraine, Kyiv&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyc_eu"&gt;@rubyc_eu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyC is a European conference devoted to Ruby, Rails and other related technologies.&lt;/p&gt;

&lt;p&gt;Every year we invite hundreds of Ruby enthusiasts and developers from various programming groups to exchange knowledge, discuss the latest news, learn from one another, and generally have a wonderful time in Kyiv, Ukraine.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyrussia.club/"&gt;RubyRussia&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; unknown, end of September&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇷🇺Russia, Moscow&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/railsclub_ru"&gt;@railsclub_ru&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conference is devoted to all aspects of Ruby and Rails development. We are the largest Ruby focused pro conference in Russia.&lt;br&gt;
Ruby Russia is a unique place where you can socialize with colleagues, receive and share experience, meet old friends and learn something new.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconfth.com/"&gt;RubyConf Thailand&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; October 16 — 17&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇹🇭Thailand, Bangkok&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconfth"&gt;@rubyconfth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf TH is a community and volunteer-powered event. We also depend on the awesomeness of the whole Ruby community to contribute to a terrific experience. So help us to make it a success!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconf.org/"&gt;RubyConf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; November 18-20&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Nashville&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconf"&gt;@rubyconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf is the world’s largest and longest-running gathering of Ruby enthusiasts, practitioners, and companies.&lt;/p&gt;

&lt;p&gt;Please, let me know if I missed any event.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>events</category>
      <category>conferences</category>
    </item>
    <item>
      <title>Continuous deployment Ruby application to Minikube with Google's Skaffold</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 21 Jan 2020 18:44:39 +0000</pubDate>
      <link>https://forem.com/amplifr/continuous-deployment-to-minikube-with-skaffold-18ib</link>
      <guid>https://forem.com/amplifr/continuous-deployment-to-minikube-with-skaffold-18ib</guid>
      <description>&lt;p&gt;&lt;a href="http://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; has made it very easy to deploy and scale applications to the cloud than ever. Still, the development process has not evolved at the same speed.&lt;/p&gt;

&lt;p&gt;Today, most developers try to either run parts of the infrastructure locally with docker or test these integrations directly in the cluster via CI jobs or the "&lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;docker push&lt;/code&gt;, &lt;code&gt;kubectl apply&lt;/code&gt;" cycle. Manual deployment is painful and incredibly slow.&lt;/p&gt;

&lt;p&gt;In this article, we will see how to set up continuous deployment of simple Ruby application to Minikube to reduce the development cycle time using the &lt;a href="https://skaffold.dev/"&gt;Google's Skaffold tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Skaffold handles the workflow for building/pushing Docker images and deploying your application. It helps to reuse your actual application production configuration (Kubernetes manifests, Helm chart, Docker Compose config) for the local continuous development cycle. Skaffold also supports file sync to prevent the full container rebuild/redeploy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Preparation
&lt;/h1&gt;

&lt;p&gt;First of all, let's install the required tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Minikube
&lt;/h2&gt;

&lt;p&gt;Here are the commands to run for Mac, more details could be found &lt;a href="https://kubernetes.io/docs/tasks/tools/install-minikube/"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;minikube
&lt;span class="nv"&gt;$ &lt;/span&gt;minikube version
minikube version: v1.5.2
commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad-dirty
&lt;span class="nv"&gt;$ &lt;/span&gt;minikube start

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube docker-env&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl config use-context minikube

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;eval $(minikube docker-env)&lt;/code&gt; set the docker client to use Minikube's Docker engine. It allows us to avoid docker image pushing and simplifies the full redeploy cycle.&lt;/p&gt;

&lt;p&gt;We've also changed to actual &lt;code&gt;kubectl&lt;/code&gt; context to minikube.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Skaffold
&lt;/h2&gt;

&lt;p&gt;Here are the documentation details of &lt;a href="https://skaffold.dev/docs/install/"&gt;Skaffold installing&lt;/a&gt;, for running on Mac just use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;skaffold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Ruby/Rack application
&lt;/h1&gt;

&lt;p&gt;We will use the simplest Rack-application from the &lt;a href="https://github.com/GoogleContainerTools/skaffold/tree/master/examples/ruby"&gt;Skaffold's github&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:GoogleContainerTools/skaffold.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples/ruby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Skaffold dev
&lt;/h2&gt;

&lt;p&gt;Just run &lt;code&gt;skaffold dev&lt;/code&gt; and you will see similar output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;skaffold dev
Listing files to watch...
 - ruby-example
Generating tags...
 - ruby-example -&amp;gt; ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Not found. Building
Found &lt;span class="o"&gt;[&lt;/span&gt;minikube] context, using &lt;span class="nb"&gt;local &lt;/span&gt;docker daemon.
Building &lt;span class="o"&gt;[&lt;/span&gt;ruby-example]...
Sending build context to Docker daemon  6.144kB
Step 1/6 : FROM ruby:2.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fb53c5f433da
Step 2/6 : WORKDIR /app
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0c2d04fabca0
Step 3/6 : ADD Gemfile&lt;span class="k"&gt;*&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 06d6623561fc
Step 4/6 : RUN bundle &lt;span class="nb"&gt;install&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 9bbcf218dda5
Step 5/6 : ADD &lt;span class="nb"&gt;.&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 478cca6b9c4f
Step 6/6 : CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bundle"&lt;/span&gt;,&lt;span class="s2"&gt;"exec"&lt;/span&gt;,&lt;span class="s2"&gt;"puma"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;434b1923c060
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 988d9e9e7bf4
Successfully built 988d9e9e7bf4
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used &lt;span class="k"&gt;in &lt;/span&gt;deployment:
 - ruby-example -&amp;gt; ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
   &lt;span class="nb"&gt;local &lt;/span&gt;images can&lt;span class="s1"&gt;'t be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - service/ruby created
 - deployment.apps/ruby created
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] Puma starting in single mode...
[ruby-75c5b89895-rd2lb ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-75c5b89895-rd2lb ruby] * Min threads: 0, max threads: 16
[ruby-75c5b89895-rd2lb ruby] * Environment: development
[ruby-75c5b89895-rd2lb ruby] * Listening on tcp://0.0.0.0:9292
[ruby-75c5b89895-rd2lb ruby] Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that time Skaffold tool has built the Docker image, tagged it with a specific tag, and deployed application to Minikube cluster. Then it  started to wait for the source file changes.&lt;/p&gt;

&lt;p&gt;The result could be seen here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
Hello Skaffold!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hot reload
&lt;/h2&gt;

&lt;p&gt;As configured at the &lt;a href="https://github.com/GoogleContainerTools/skaffold/blob/master/examples/ruby/skaffold.yaml#L10-L11"&gt;Skaffold config&lt;/a&gt; - it watches for &lt;code&gt;*.rb&lt;/code&gt; files and reloads all the changes rights into the container sources. The sample Rack application also supports for reloading with help of the &lt;code&gt;rack-unreloader&lt;/code&gt; ruby-gem.&lt;/p&gt;

&lt;p&gt;If we change the &lt;code&gt;app.rb&lt;/code&gt; file to return other string, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Hello Skaffold!!!"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will see skaffold sync files into the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Syncing 1 files &lt;span class="k"&gt;for &lt;/span&gt;ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
Watching &lt;span class="k"&gt;for &lt;/span&gt;changes...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the Rack application supports file reloading, it will now produce the different output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
Hello Skaffold!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic redeploy
&lt;/h2&gt;

&lt;p&gt;If we change others files, such as &lt;code&gt;backend/Dockerfile&lt;/code&gt;, Skaffold will autmatically rebuild the Docker image, and redeploy the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"gem 'oj'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; backend/Gemfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll see full redeploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Generating tags...
 - ruby-example -&amp;gt; ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Not found. Building
Found &lt;span class="o"&gt;[&lt;/span&gt;minikube] context, using &lt;span class="nb"&gt;local &lt;/span&gt;docker daemon.
Building &lt;span class="o"&gt;[&lt;/span&gt;ruby-example]...
Sending build context to Docker daemon  6.144kB
Step 1/6 : FROM ruby:2.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fb53c5f433da
Step 2/6 : WORKDIR /app
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0c2d04fabca0
Step 3/6 : ADD Gemfile&lt;span class="k"&gt;*&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 7b4671e7469d
Step 4/6 : RUN bundle &lt;span class="nb"&gt;install&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0331311c04f3
Step 5/6 : ADD &lt;span class="nb"&gt;.&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 67e2cf138ed3
Step 6/6 : CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bundle"&lt;/span&gt;,&lt;span class="s2"&gt;"exec"&lt;/span&gt;,&lt;span class="s2"&gt;"puma"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;fa04b480dd8f
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 31f2e1f54283
Successfully built 31f2e1f54283
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used &lt;span class="k"&gt;in &lt;/span&gt;deployment:
 - ruby-example -&amp;gt; ruby-example:31f2e1f5428397d5d4aa1f2d2328ff859e01d970f304f2f9dbb9f4ba8d0c8968
   &lt;span class="nb"&gt;local &lt;/span&gt;images can&lt;span class="s1"&gt;'t be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - deployment.apps/ruby configured
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] - Gracefully stopping, waiting for requests to finish
[ruby-75c5b89895-rd2lb ruby] === puma shutdown: 2020-01-20 11:49:02 +0000 ===
[ruby-75c5b89895-rd2lb ruby] - Goodbye!
[ruby-5b74868996-ckg24 ruby] Puma starting in single mode...
[ruby-5b74868996-ckg24 ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-5b74868996-ckg24 ruby] * Min threads: 0, max threads: 16
[ruby-5b74868996-ckg24 ruby] * Environment: development
[ruby-5b74868996-ckg24 ruby] * Listening on tcp://0.0.0.0:9292
[ruby-5b74868996-ckg24 ruby] Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the &lt;code&gt;app.rb&lt;/code&gt; then to return json string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'oj'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Oj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the &lt;code&gt;app.rb&lt;/code&gt; file synchronization, the app would return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;File sync and automatic &lt;code&gt;rebuild/redeploy&lt;/code&gt; are the core.&lt;/p&gt;

&lt;p&gt;But, Skaffold has many essential features for Kubernetes-native development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;policy-based image tagging&lt;/li&gt;
&lt;li&gt;resource port-forwarding&lt;/li&gt;
&lt;li&gt;supports multiple profiles&lt;/li&gt;
&lt;li&gt;supports Helm, Kustomize, Docker Compose manifests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Skaffold is simple and lightweight tool, easy to get up and running for local development! It automates deployments, but for comfortable development I like it to have any other features, like backward file sync.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>kubernetes</category>
      <category>development</category>
      <category>skaffold</category>
    </item>
    <item>
      <title>Dockerize the multi-services application for local development</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 15 Apr 2019 11:40:13 +0000</pubDate>
      <link>https://forem.com/amplifr/dockerize-the-multi-services-application-for-local-development-2oig</link>
      <guid>https://forem.com/amplifr/dockerize-the-multi-services-application-for-local-development-2oig</guid>
      <description>&lt;p&gt;As nowadays many complex web-applications run on production containerized, we keep developing them in 'old-school' way, installing the &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;Postgresql&lt;/a&gt;, &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, &lt;a href="https://www.ruby-lang.org" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt; and other components on the local development machine.&lt;/p&gt;

&lt;p&gt;It is getting harder to &lt;strong&gt;maintain the process of development&lt;/strong&gt;, especially when the system becomes heterogeneous and expands into a large number of services, running with various versions of the dependent components. It becomes especially actual when the dependent component's versions are varied.&lt;/p&gt;

&lt;p&gt;In this article, I am going to review the local development containerization with the example of the &lt;a href="https://amplifr.com" rel="noopener noreferrer"&gt;Amplifr&lt;/a&gt;, the project I work on. With the help of docker-compose and docker networks, it's easy and efficient.&lt;/p&gt;

&lt;p&gt;As all the infrastructure are containerized, and are managed with Kubernetes on production, we will attend the setting up &lt;strong&gt;local development only&lt;/strong&gt;, following the one principle - &lt;strong&gt;the convenience of the development process&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of the local containerization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No need to install all the components, such as database engines, language interpreters on the local machine. It keeps local machine &lt;em&gt;clean&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;natural support of the different environments, g.e. running the services with different versions of Ruby, of Postgresql on the local machine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project overview
&lt;/h2&gt;

&lt;p&gt;However Amplifr's backend is running on Rails, the project also has the complicated frontend, serving by the standalone Node.js server and the Logux web-socket server, and other helper-services, written on Node.js, Ruby, and Golang.&lt;/p&gt;

&lt;p&gt;The following picture shows the simplified architecture of the project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nznv7aj9qbsj54lek49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nznv7aj9qbsj54lek49.png" alt="Overall Amplifr's services map" width="791" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am going to quickly review some components of the overall system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend server
&lt;/h3&gt;

&lt;p&gt;The backend is the classic Rails-application, doing all the business logic and performing many background jobs with Sidekiq.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend server
&lt;/h3&gt;

&lt;p&gt;The frontend is the only public HTTP entry-point for the overall application. It serves the frontend assets and proxies other requests to the Rails backend.&lt;br&gt;
The backend is also integrated back with frontend server for sharing some data, &lt;a href="https://dev.to/amplifr/outdated-browser-detection-with-browserslist-10co"&gt;like &lt;code&gt;browsers.json&lt;/code&gt;&lt;/a&gt; file for proper rendering of HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqnkjxwahna53q0oy1kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqnkjxwahna53q0oy1kl.png" alt="Frontend-backend integration" width="751" height="191"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Logux server
&lt;/h3&gt;

&lt;p&gt;The Logux is the server exposing the web-socket port, holding the bidirectional connection with the client's browsers. To perform the business logic, it has two ways of HTTP integration with the backend. It allows us to keep all the business logic in Rails-backend, and sending back the notifications from backend by hitting Logux with HTTP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69j7ulpl5zdx0ox5yqrn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69j7ulpl5zdx0ox5yqrn.png" alt="Logux-backend integration" width="751" height="229"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  "Link shortener"
&lt;/h3&gt;

&lt;p&gt;The link shortener is the specific web-service, written with Golang. It aims for shortening a link, expanding them and manages the overall statistics about link expansions.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31zguz9tbgwbiephm3l8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31zguz9tbgwbiephm3l8.png" alt="Link shortener server integration with backend" width="751" height="191"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  "Preview" service
&lt;/h3&gt;

&lt;p&gt;The preview is the public service, used from the client browsers to render the OpenGraph representation of any link. It has public http endpoint only.&lt;/p&gt;
&lt;h3&gt;
  
  
  Other components
&lt;/h3&gt;

&lt;p&gt;Shortener - is the standalone service for shorting the url and keeping analytics data about link expansion. It is written with Golang. It has the external public endpoint to expand the shorted links, and internal endpoint to short the links while publication social content within backend's background jobs.&lt;/p&gt;

&lt;p&gt;And some other internal services, such as telegram and facebook bot, which have the backend only integrations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Components dependents
&lt;/h2&gt;

&lt;p&gt;Most of the components are by itself the complex web-services, depending on underlying components, such as Postgres, Redis, and other services low-level system services.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqhrh4c0k1brx5b3fqk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqhrh4c0k1brx5b3fqk4.png" alt="Internals of the backend component and how we are going to dockerize it" width="736" height="251"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Containarization
&lt;/h3&gt;

&lt;p&gt;💡We will containerize each service separately with the &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;. It is a tool for defining and running multi-container Docker applications, making it easy to start just by running up all the services together with only one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡To make the services to integrate we will use the &lt;a href="https://docs.docker.com/network/" rel="noopener noreferrer"&gt;docker networks&lt;/a&gt;, that allows any docker containers to communicate with each other. We will use only one &lt;code&gt;internal&lt;/code&gt; docker network for all the components for simplicity. Being more accurate a reader will be able to set up the individual network for every service dependents and for every group of the connectivity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy55d7ccil8ixtnmm1wp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy55d7ccil8ixtnmm1wp2.png" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerize Ruby Backend
&lt;/h3&gt;

&lt;p&gt;Here we have the standard stack: Postgres, Redis, Rails web-server and the Sidekiq background. For all of them, we'll define the services in &lt;code&gt;docker-compose.yaml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here are the key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for the Postgres and Redis, we will define the &lt;a href="https://docs.docker.com/storage/volumes/" rel="noopener noreferrer"&gt;persistent volumes&lt;/a&gt; to save the data between the runs&lt;/li&gt;
&lt;li&gt;we are not going to copy the Ruby source code into the container, instead of this - we will mount the Rails application source-code to the &lt;code&gt;/app&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;we also will define the persistent storage for the bundle and other stuff to increase the next time starts&lt;/li&gt;
&lt;li&gt;we will define the &lt;code&gt;amplifr_internal&lt;/code&gt; network and add the interacting containers to that network&lt;/li&gt;
&lt;li&gt;the application should be ready to be configured with the environments variables, which we are going to set up in docker-compose file&lt;/li&gt;
&lt;li&gt;we will define the base application service in YAML file and then will use the Anchors and aliases of the YAML syntax not to repeat yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗Keep in mind, that this configuration differs from the way of building docker image for production, where all the source code and all the dependency bundles are copied inside the docker image, to let it be all-sufficient and not to have external dependencies!&lt;/p&gt;

&lt;p&gt;Here is the full &lt;a href="https://gist.github.com/dsalahutdinov/2d8975347ec79e94e7674dd0d9d4c518" rel="noopener noreferrer"&gt;gist with all the config&lt;/a&gt;, but let me pay attention to the main points:&lt;/p&gt;

&lt;h3&gt;
  
  
  Describe the base-service to inherit from it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PG_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.6'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amplifr-dev:0.1.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# environment settings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_CONFIG=/app/.bundle/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=${RAILS_ENV:-development}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://postgres@postgres/amplifr_${RAILS_ENV}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379/&lt;/span&gt;

      &lt;span class="c1"&gt;# service integrations&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FRONTEND_URL=https://frontend-server:3001/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LOGUX_URL=http://logux-server:31338&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;tmpfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base service's container will be build from the &lt;code&gt;Dockerfile.dev&lt;/code&gt; with the arguments - the Postgres version. All other Ruby based images will inherit the base. Here is the service inheritance diagram:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpswyimt56yboaqgpkdux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpswyimt56yboaqgpkdux.png" alt="Service inheritance" width="686" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also define the mapping of the current folder to the container's &lt;code&gt;/app&lt;/code&gt; directory and mount the docker volume for the bundles. It prevents every time dependencies installation.&lt;/p&gt;

&lt;p&gt;We also define two groups of the environments variables:&lt;br&gt;
1) &lt;code&gt;system&lt;/code&gt; variables, such as &lt;code&gt;BUNDLE_PATH&lt;/code&gt;, &lt;code&gt;REDIS_URL&lt;/code&gt; and &lt;code&gt;DATABASE_URL&lt;/code&gt; URLs.&lt;br&gt;
2) dependent services internal url for integration:&lt;br&gt;
&lt;code&gt;FRONTEND_URL&lt;/code&gt; - is the internal endpoint of the frontend server to get the supported browserslist.&lt;br&gt;
&lt;code&gt;LOGUX_URL&lt;/code&gt; - is the internal Logux HTTP endpoint for sending action from Rails-app to Logux.&lt;/p&gt;
&lt;h3&gt;
  
  
  Describe the 'runner'
&lt;/h3&gt;

&lt;p&gt;The runner service is for running maintaining commands, such as rake tasks, or generators in Rails-environment. It is console-oriented service, so we have to go set up &lt;code&gt;stdin_open&lt;/code&gt; and &lt;code&gt;tty&lt;/code&gt; options, which corresponds to the &lt;code&gt;-i&lt;/code&gt; and &lt;code&gt;--t&lt;/code&gt; option of docker and enable bash shell for the container start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*backend&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use it in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run runner bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:create

&lt;span class="c"&gt;# or run container and any command within the container&lt;/span&gt;
docker-compose run runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose the server
&lt;/h3&gt;

&lt;p&gt;Define the web-server. The critical point here is that we define additional docker network &lt;code&gt;internal&lt;/code&gt; and adds the web-server to it giving the &lt;code&gt;backend-server&lt;/code&gt; alias to the container host in this network. So the web container will be accessible with the &lt;code&gt;backend-server&lt;/code&gt; network name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec thin start&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend-server&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3000:3000'&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose the Sidekiq
&lt;/h3&gt;

&lt;p&gt;Easy, it just runs the sidekiq and inherits the base service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sidekiq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sidekiq&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose Redis and Postgres
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:9.6&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:3.2-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis:/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main point here is that we mount the volumes for the container's paths, where the data is stored. It persists the data between runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;We would not dive deep into writing the &lt;code&gt;Dockefile&lt;/code&gt;. You can find it &lt;a href="https://gist.github.com/dsalahutdinov/2d8975347ec79e94e7674dd0d9d4c518" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Just notice, that it inherites from the standard ruby image, some required components such as Postgresql client and some other binaries to build the bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;The usage is quite easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run runner ./bin/setup &lt;span class="c"&gt;# runs the bin/setup in docker&lt;/span&gt;
docker-compose run runner bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:drop &lt;span class="c"&gt;# runs rake task&lt;/span&gt;
docker-compose up server &lt;span class="c"&gt;# get the web-server running&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="c"&gt;# runs all the services (web, sidekiq)&lt;/span&gt;
docker-compose up rails db &lt;span class="c"&gt;# runs the postgres client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Compose also allows to specify the service dependencies and get the dependent service up if it is needed for the running service, g.e. Sidekiq requires the Redis and Postgres services to work correctly, that is why we define them in the &lt;code&gt;depends_on&lt;/code&gt; section of the service.&lt;/p&gt;

&lt;p&gt;And here is the service dependency diagram, showing how the services run:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vgvrryqlcr9irnz9xsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vgvrryqlcr9irnz9xsq.png" alt="Service startup sequence in case of depencencies" width="371" height="244"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;We've got the Rails-application running locally for the development. It works the same way as the local: persists the database, runs the rake task. Also the commands like &lt;code&gt;rails db&lt;/code&gt;, &lt;code&gt;rails c&lt;/code&gt; works well within a container.&lt;/p&gt;

&lt;p&gt;The main advantage is that we can change the Postgres version or the Ruby version easily by changing one line, then rebuild image and try to run with the new environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dockerize Node.js (frontend server)
&lt;/h2&gt;

&lt;p&gt;The primary key points here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the base official &lt;code&gt;node&lt;/code&gt; docker images without any tuning&lt;/li&gt;
&lt;li&gt;add the &lt;code&gt;server&lt;/code&gt; service to the &lt;code&gt;amplifr_internal&lt;/code&gt; network&lt;/li&gt;
&lt;li&gt;define the &lt;code&gt;BACKEND_URL&lt;/code&gt; environment variable to map to the internal docker path of the backend service.&lt;/li&gt;
&lt;li&gt;mount the &lt;code&gt;mode_modules&lt;/code&gt; volume for the Node.js modules install path
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:11&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BACKEND_URL=http://backend-server:3000&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;

  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "yarn cache clean &amp;amp;&amp;amp; yarn install &amp;amp;&amp;amp; yarn start"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend-server&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3001"&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;The frontend server is now easy to start, by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it needs the backend to start first because frontend service refers to the &lt;code&gt;internal&lt;/code&gt; network, which gets up while starting up the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerize the Logux server
&lt;/h3&gt;

&lt;p&gt;In any simple case, Logux server has any databases dependencies and could be configured the same way as frontend. The only one difference, that Logux service has its environment variables, to set up the interaction with integrated services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up server &lt;span class="c"&gt;# runs the server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dockerizing Golang (link shortener web service)
&lt;/h2&gt;

&lt;p&gt;The main idea is also the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the set up docker image with &lt;code&gt;Golang&lt;/code&gt;, mount the application source code there and run it with the &lt;code&gt;go run&lt;/code&gt; interpreter.&lt;/li&gt;
&lt;li&gt;share the service with docker networks for integrate with Ruby backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our web-service has the Postgres and Redis dependencies. Lets start describing from the &lt;code&gt;Dockerfile&lt;/code&gt;, overall config sample can be found &lt;a href="https://gist.github.com/dsalahutdinov/47487f319eeaa12cb2026daf9c06f8f0" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.11&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; MIGRATE_VERSION=4.0.2&lt;/span&gt;

&lt;span class="c"&gt;# install postgres client for local development&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql-client

&lt;span class="c"&gt;# install dep tool to ensuring dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/golang/dep/cmd/dep

&lt;span class="c"&gt;# install migrate cli for running database migrations&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://github.com/golang-migrate/migrate/releases/download/v${MIGRATE_VERSION}/migrate.linux-amd64.tar.gz /tmp&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; /tmp/migrate.linux-amd64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /usr/local/bin/migrate.linux-amd64 /usr/local/bin/migrate

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP ${GOPATH}/src/github.com/evilmartians/ampgs&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ${APP}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are a couple of interesting details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we install postgres-client for local development image. It simplifies the access to the database, whenever you need it: &lt;code&gt;docker-compose run runner "psql $DATABASE_URL"&lt;/code&gt;. The same we have at the Ruby backend dockerization&lt;/li&gt;
&lt;li&gt;we install the &lt;code&gt;dep&lt;/code&gt; tool to install and ensure all the dependencies: &lt;code&gt;docker-compose run runner dep ensure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;we install the migration tool to the image, to allow do database migrations right from the docker container: &lt;code&gt;docker-compose run runner "migrate -source file://migrations/ -database ${DATABASE_URL} up"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‼️ The most of those tool we do not need for the production environment docker image, because it will contain only compiled binary.&lt;/p&gt;

&lt;p&gt;We will use the same way of dockerizing to Golang service, as the Ruby service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extract the base &lt;code&gt;app&lt;/code&gt; service and the special &lt;code&gt;runner&lt;/code&gt; service for run the maintenance tasks&lt;/li&gt;
&lt;li&gt;add the Postgres and Redis dependencies with persistible data volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the significant parts of the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# base service definition&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ampgs:0.3.1-development&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/development/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379/6&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:postgres@postgres:5432/ampgs&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/go/src/github.com/evilmartians/ampgs&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;go&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ampgs.go"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8000:8000'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ampgs-server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Wrap up
&lt;/h1&gt;

&lt;p&gt;Docker-compose is the powerful tool to simplify the managing of the complex services.&lt;br&gt;
Let me review the main principles for local development dockerization in context of using docker compose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mount the source code as the folder to the container instead of rebuilding docker&lt;/strong&gt; image with the copy of source code. It helps a lot of time for every local restart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;use the docker networks to craft the communication between services&lt;/strong&gt;. It helps to test all the services together, but keeps their environments separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;services get to know of each other&lt;/strong&gt; by providing the environments variables to the docker container with the &lt;code&gt;docker-compose&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>node</category>
      <category>go</category>
      <category>docker</category>
    </item>
    <item>
      <title>Monitoring Puma web server with Prometheus and Grafana</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 02 Apr 2019 10:48:27 +0000</pubDate>
      <link>https://forem.com/amplifr/monitoring-puma-web-server-with-prometheus-and-grafana-5b5o</link>
      <guid>https://forem.com/amplifr/monitoring-puma-web-server-with-prometheus-and-grafana-5b5o</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Localizing web application performance problems and response latency could be tricky in the projects with complex infrastructure.&lt;/p&gt;

&lt;p&gt;And having &lt;strong&gt;monitoring&lt;/strong&gt; for all the services is highly crucial.&lt;/p&gt;

&lt;p&gt;Sometimes performance degradation might be induced &lt;em&gt;one step ahead of the main application&lt;/em&gt; because of the lack of the web server capacity. As the most popular web server for running Ruby web applications is &lt;strong&gt;Puma&lt;/strong&gt;, let me explain how to implement and tune up the simple monitoring for it. &lt;/p&gt;

&lt;h1&gt;
  
  
  Puma control application
&lt;/h1&gt;

&lt;p&gt;Puma has a &lt;strong&gt;built-in control application&lt;/strong&gt; for managing web-server and asking it's internal statistics. Control application actives by the following code, called from Puma configuration file &lt;code&gt;puma/config.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;activate_control_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tcp://127.0.01:9000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auth_token: &lt;/span&gt;&lt;span class="s1"&gt;'top_secret'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or without any params, just activate_control_app&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For serving it, Puma runs the &lt;a href="https://github.com/puma/puma/blob/master/lib/puma/runner.rb#L47" rel="noopener noreferrer"&gt;separate web-server instance&lt;/a&gt; with the specific &lt;a href="https://github.com/puma/puma/blob/master/lib/puma/app/status.rb" rel="noopener noreferrer"&gt;rack backend application&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This backend returns the worker statistics data in JSON format:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# GET /stats?secret=top_secret&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"booted_workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"old_workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"worker_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"pid"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"booted"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_checkin"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2019-03-31T13:04:28Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"pid"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"booted"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_checkin"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2019-03-31T13:04:28Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The exact answer schema depends on the Puma configuration: when it is in clustered mode (has more than one worker), the output describes each worker.&lt;br&gt;
If Puma is in non-clustered, the result describes only the single worker with rapid output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Anyway, there are meaningful metrics for monitoring purposes, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_threads&lt;/code&gt; -  preconfigured maximum number of worker threads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt; - the number of running threads (spawned threads) for any Puma worker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pool_capacity&lt;/code&gt; - the number of requests that the server is capable of taking right now. More details are here.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;backlog&lt;/code&gt; - the number of connections in that worker's "todo" set waiting for a worker thread&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using them, we can automate the monitoring system, which checks the values periodically any Puma cluster and show metrics in dynamic, such as shown below:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva2b8h92fv787me9gu6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva2b8h92fv787me9gu6l.png" alt="Puma workers pool capacity chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Decreasing &lt;code&gt;pool_capacity&lt;/code&gt; means raising the load of the server. It is the starting point of rising the request processing time latency by capacity issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yabeda framework
&lt;/h2&gt;

&lt;p&gt;For the Ruby world, we have the extendable framework for collecting and exporting metrics, which is called &lt;a href="https://github.com/yabeda-rb/yabeda" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It provides a simple DSL for describing the metrics and fetching their values with a simple lambda function. &lt;/p&gt;

&lt;p&gt;For now, &lt;a href="https://evilmartians.com/chronicles/meet-yabeda-modular-framework-for-instrumenting-ruby-applications" rel="noopener noreferrer"&gt;Yabeda framework&lt;/a&gt; provides solutions for monitoring &lt;a href="https://github.com/yabeda-rb/yabeda-rails" rel="noopener noreferrer"&gt;Rails&lt;/a&gt; and &lt;a href="https://github.com/yabeda-rb/yabeda-sidekiq" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt; out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus
&lt;/h2&gt;

&lt;p&gt;Monitoring considers periodically storing the metric values for future analysis. And one of the most popular and suitable solutions for that is &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;. As Prometheus implements the "HTTP pull model," it expects the monitorable subject to expose some endpoint with the metrics value in the specific format.&lt;/p&gt;

&lt;p&gt;Yabeda framework allows exporting metrics with the help of &lt;a href="https://github.com/yabeda-rb/yabeda-prometheus" rel="noopener noreferrer"&gt;Prometheus Exporter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yabeda for Puma
&lt;/h2&gt;

&lt;p&gt;Now I am going to introduce one more new monitoring solution of the Yabeda family - &lt;a href="https://github.com/yabeda-rb/yabeda-puma-plugin" rel="noopener noreferrer"&gt;puma monitoring plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It just needs to load the &lt;code&gt;yabeda-puma-plugin&lt;/code&gt; gem and to &lt;a href="https://github.com/yabeda-rb/example-prometheus/blob/master/rails_app/config/puma.rb#L19-L20" rel="noopener noreferrer"&gt;configure Puma web server&lt;/a&gt; with following lines in &lt;code&gt;puma/config.rb&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;activate_control_app&lt;/span&gt;
&lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:yabeda&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it. After the Puma web server start, the plugin will do all the job for collection the metrics.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get things together
&lt;/h1&gt;

&lt;p&gt;Here is the overall architecture of the Puma monitoring solution:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynq6005bljq7n0zepgln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynq6005bljq7n0zepgln.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It gets all the metrics from Puma control application statistics and consolidates them to the Yabeda framework. Values could be exported by Prometheus rack-middleware, serving the &lt;code&gt;/metrics&lt;/code&gt; path of the web application and providing metrics values in prometheus-friendly format. Here is the sample response of metrics endpoint for Puma, configured with two workers:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

GET /metrics

puma_backlog{index="0"} 0
puma_backlog{index="1"} 0
puma_running{index="0"} 5
puma_running{index="1"} 5
puma_pool_capacity{index="0"} 1
puma_pool_capacity{index="1"} 5
puma_max_threads{index="0"} 5
puma_max_threads{index="1"} 5
puma_workers 2
puma_booted_workers 2
puma_old_workers 0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Visualization
&lt;/h2&gt;

&lt;p&gt;Depending on your needs, the data could be visualized in many ways; here is the example of basic summarized metrics values:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsvyvdmjbdi4nutsaqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsvyvdmjbdi4nutsaqv.png" alt="Basic summarized values values visualization in Grafana"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This diagram shows the overall metrics values for all the Puma workers. Also, indicators could be displayed separately for all the workers, or all the Puma cluster instances.&lt;/p&gt;
&lt;h2&gt;
  
  
  "Application busy" metric
&lt;/h2&gt;

&lt;p&gt;Looking at all the raw Puma metrics might be not visually comfortable to make some quick overview of the system in general. More suitable way if to calculate the composite metric, describing the overall workload of the web-server in percentage. Let call it &lt;strong&gt;"Application busy"&lt;/strong&gt; or just &lt;strong&gt;busy-metric&lt;/strong&gt;. Formula evaluates the percentage of overall workload:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(1 - pool_capacity / max_threads) * 100


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It turns out to have the only chart instead of several:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxih9ljc3ou6srgv5ikv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxih9ljc3ou6srgv5ikv.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;busy-metric&lt;/strong&gt; looks to be more informative to overview the health of the system. It shows the actual workload of overall Puma cluster in a more friendly way. When busy-metrics sticks up, it means that the application is under high load, and it probably needs to tune up the Puma web server. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Busy-metric&lt;/strong&gt; allows to determine the problem state easily,  but for specific incident investigation, raw metrics might be more helpful and advisable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Metrics playground
&lt;/h1&gt;

&lt;p&gt;Yabeda framework supply the example project with all the monitoring infrastructure set up for &lt;a href="https://github.com/yabeda-rb/example-prometheus" rel="noopener noreferrer"&gt;monitoring the Sidekiq, Rails, and Puma&lt;/a&gt;. It is easy to set it up with docker-compose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapup
&lt;/h1&gt;

&lt;p&gt;Setting up the monitoring infrastructure makes to build more stable and maintainable software, and sleep calmly at night. &lt;br&gt;
Monitoring is made easy with &lt;a href="https://github.com/yabeda-rb/yabeda" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt; framework.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/yabeda-rb/yabeda-puma-plugin" rel="noopener noreferrer"&gt;yabeda-puma-plugin&lt;/a&gt; for getting ready to monitor Puma!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>monitoring</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Outdated browser detection with Browserslist</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 20 Nov 2018 19:43:22 +0000</pubDate>
      <link>https://forem.com/amplifr/outdated-browser-detection-with-browserslist-10co</link>
      <guid>https://forem.com/amplifr/outdated-browser-detection-with-browserslist-10co</guid>
      <description>&lt;p&gt;The standard way to configure target browsers with Node.js is &lt;a href="https://github.com/browserslist/browserslist" rel="noopener noreferrer"&gt;Browserslist&lt;/a&gt;. It is possible to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browserslist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"last 2 version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"not dead"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to the &lt;code&gt;package.json&lt;/code&gt; or the &lt;code&gt;.browserslistrc&lt;/code&gt; config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Browsers that we support
last 2 version
not dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two similar examples mean that the target browsers are two last version and the browser is not dead.&lt;/p&gt;

&lt;p&gt;This config is used by many front-end tools, such as &lt;a href="https://github.com/postcss/autoprefixer" rel="noopener noreferrer"&gt;Autoprefixer&lt;/a&gt;, &lt;a href="https://github.com/babel/babel/tree/master/packages/babel-preset-env" rel="noopener noreferrer"&gt;Babel&lt;/a&gt; and many others.&lt;/p&gt;

&lt;p&gt;But in this article I am going to write about the &lt;a href="https://github.com/browserslist/browserslist-useragent" rel="noopener noreferrer"&gt;Browserslist Useragent&lt;/a&gt; frontend tool for finding if a given user agent string satisfies a &lt;code&gt;Browserslist&lt;/code&gt; browsers:&lt;/p&gt;

&lt;p&gt;Install the &lt;code&gt;browserslist-useragent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;browserslist-useragent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you can determine by &lt;code&gt;User-Agent&lt;/code&gt; string if you browser matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;matchesUA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist-useragent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgentString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// with browserslist config inferred&lt;/span&gt;
&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//returns boolean&lt;/span&gt;

&lt;span class="c1"&gt;// with explicit browserslist&lt;/span&gt;
&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Firefox &amp;gt; 53&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="c1"&gt;// returns true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imaging we have the &lt;code&gt;.browserslistrc&lt;/code&gt; config file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;last 2 versions
not IE 11
not ExplorerMobile 11
not last 1 OperaMini version
not OperaMobile 12.1
not dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the array of detailed browsers rules with the help of browserslist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browserslist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./browsers.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;browserslist&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the sample above it will produce the json file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_chr 67"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_ff 60"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_qq 1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_uc 11.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"android 67"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"android 4.4.3-4.4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"baidu 7.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"chrome 69"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"chrome 68"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"edge 17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"edge 16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"firefox 62"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"firefox 61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"ios_saf 11.3-11.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"ios_saf 11.0-11.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"op_mob 46"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"opera 55"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"opera 54"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"safari 11.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"safari 11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"samsung 7.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"samsung 6.2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the way to determine the browsers matchings with Node.js.&lt;/p&gt;

&lt;p&gt;Why do we need to check the browsers version on both sides: backend and frontend?&lt;br&gt;
In the case of your modern javascript frontend would not be loaded on the old browser - we can still use the backend rendering to write some HTML notifying user about the issue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcetyzxqok39u431oi2hy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcetyzxqok39u431oi2hy.png" alt="The outdated browser html block sample" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗This HTML block would work in any browser no matter how old it is.&lt;/p&gt;

&lt;p&gt;And if your backend is written with Ruby - use can still use the port of the original tool to the Ruby - &lt;a href="https://github.com/browserslist/browserslist-useragent-ruby" rel="noopener noreferrer"&gt;browserslist-useragent gem&lt;/a&gt;. It works the same way its Node.js version - recognises the family and the version from the &lt;code&gt;User-Agent&lt;/code&gt; header string and matches it with the &lt;code&gt;browserslist&lt;/code&gt;-rules produced by the &lt;code&gt;Browserslists&lt;/code&gt; tool.&lt;/p&gt;
&lt;h1&gt;
  
  
  Single project
&lt;/h1&gt;

&lt;p&gt;The usage is straightforward - it just needs you to generate the &lt;code&gt;browsers.json&lt;/code&gt; file before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supported_browser?&lt;/span&gt;
    &lt;span class="vi"&gt;@browsers&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"browsers.json"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BrowserslistUseragent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;allow_higher: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:supported_browser?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this code to you Rails-application layout template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haml"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;supported_browser?&lt;/span&gt;
  &lt;span class="nc"&gt;.div&lt;/span&gt; 
div( style: "position: fixed; bottom: 0; right: 0; padding: 8px 10px; background: #e9502f; color: white; width: 100%; z-index: 10; text-align: center;" )
    &lt;span class="nc"&gt;.div&lt;/span&gt;
      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unsupported_browser'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❗This old-fashioned style is deliberately chosen: 'style'-attributes will work mostly everywhere!&lt;/p&gt;

&lt;p&gt;Here it is. It will work well for the Rails projects where all the frontend and backend live together as one &lt;a href="https://evilmartians.com/chronicles/evil-front-part-1" rel="noopener noreferrer"&gt;solid project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separated frontend and backend projects
&lt;/h2&gt;

&lt;p&gt;If you have separated projects for Ruby backend and Node.js frontend, you will prefer to get browsers.json over HTTP. You will need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serve the &lt;code&gt;/browsers.json&lt;/code&gt; path to render the browserslist output by putting it to the &lt;code&gt;public&lt;/code&gt; folder:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browsers.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;browserslist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;get in over HTTP in the ruby-backend code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://frontend-domain.local/browsers.json'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BrowserslistUseragent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;allow_higher: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the &lt;a href="https://github.com/plataformatec/faraday-http-cache" rel="noopener noreferrer"&gt;faraday-http-cache&lt;/a&gt; to cache the results of the http request. It will force to make one request per the Rails application instance only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# caches http response locally with etag&lt;/span&gt;
&lt;span class="n"&gt;http_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;store: &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;adapter&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_adapter&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://frontend-domain.local/browsers.json'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. This solution will use one &lt;code&gt;browserslist.rc&lt;/code&gt; config in the frontend repository, which will automatically be shared over the backend.&lt;/p&gt;

&lt;p&gt;More details abort the &lt;code&gt;browserslist_useragent&lt;/code&gt; gem you will find &lt;a href="https://github.com/browserslist/browserslist-useragent-ruby" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>node</category>
      <category>browserdetection</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Purify code with data integrity. Part 1: defaults and not-nulls</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 19 Nov 2018 18:06:47 +0000</pubDate>
      <link>https://forem.com/amplifr/purify-code-with-data-integrity-part-1-defaults-and-not-nulls-492p</link>
      <guid>https://forem.com/amplifr/purify-code-with-data-integrity-part-1-defaults-and-not-nulls-492p</guid>
      <description>&lt;p&gt;Working with the data is the crucial point of any application. &lt;strong&gt;ActiveRecord&lt;/strong&gt; makes this process easier for Rails developer. It allows managing the data without getting deep into the underlying database. Sometimes it causes some code issues we would not have if pay more attention to the database structuring and maintaining data integrity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data integrity?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Data_integrity" rel="noopener noreferrer"&gt;Data integrity&lt;/a&gt; is the process of maintaining the accuracy and consistency of the data.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;ActiveRecord&lt;/code&gt; context I mean, that &lt;code&gt;data integrity&lt;/code&gt; - is the set of the principles for &lt;em&gt;keeping an underlying database in accuracy state&lt;/em&gt; by paying more attention to its schema design.&lt;/p&gt;

&lt;p&gt;In this article, I am going to review some well-known &lt;strong&gt;data integrity techniques&lt;/strong&gt; which are very helpful in improving the codebase but sometimes are undervalued for some reasons. We will see how easy they are to integrate and how they can help to write more clear code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default values
&lt;/h2&gt;

&lt;p&gt;Have you seen the code like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example 1&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s1"&gt;'new'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example 2&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_validation&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is the same problem in both examples: the lack of default value preset. It causes for writing initialization "by hand" in a non-centralised way using one of the following options:&lt;/p&gt;

&lt;p&gt;1) 👎&lt;strong&gt;to set the value "in place"&lt;/strong&gt; (example one). This solution is fast and dirty. The main disadvantage is implicitness of the "correct" default value: someone who is not familiar with the project &lt;strong&gt;would not know the proposal default for setting up&lt;/strong&gt; in the future or &lt;strong&gt;expect the value should already present&lt;/strong&gt;. It may cause the errors.&lt;/p&gt;

&lt;p&gt;The second point is that such "code snippets" &lt;strong&gt;spread all over the codebase&lt;/strong&gt; and make it hard to maintain because of duplication and decentralization.&lt;/p&gt;

&lt;p&gt;2) 👎&lt;strong&gt;to set the default values in the "pre-save" model callback&lt;/strong&gt; &lt;br&gt;
 (example two from the top).But here we also have implicitness. Where should I find the correct initial value &lt;code&gt;before_save&lt;/code&gt;, &lt;code&gt;before_commit&lt;/code&gt;, &lt;code&gt;before_validation&lt;/code&gt;? The answer depends on the imagination of the author 😊.&lt;/p&gt;

&lt;p&gt;The second problem is that the default value is not initialized in the new model (example two), and even we set the default values before saving, we still have it uninitialized in the new model object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👍The best option is to preset the default value in the database schema and let ActiveRecord work well by design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="ss"&gt;:'new'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# default value work by-default :)&lt;/span&gt;
&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It allows to have a new object with the column set by default. ActiveRecord will read the metadata of your table and preset the default values for any new model of the &lt;code&gt;User&lt;/code&gt; type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the database
&lt;/h3&gt;

&lt;p&gt;The simple case is to set the default values while creating the new table. It is easy and non-blocking operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:order&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise adding a column with default to the existing table is more complicated, because it will enforce setting the default value for all the rows. It will lock read/write operation for a while in the high-loaded databases.&lt;/p&gt;

&lt;p&gt;If you are lucky to be on the edge and run you application on Postgres 11 - things become much simpler. PostgreSQL 11 does not touch the existing row, but &lt;a href="https://leopard.in.ua/2016/09/20/safe-and-unsafe-operations-postgresql#.W_Oy4nozZbW" rel="noopener noreferrer"&gt;updates them 'lazily'&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are not on Postgres 11, the safe way to set up default value constraint consists of two simple steps:&lt;br&gt;
1) creating the nullable column without setting default value&lt;br&gt;
2) changing the default value of the column&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
  &lt;span class="n"&gt;change_column_default&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that all new rows will have the default values and active record will use those metadata to automatically preset the default. All the existing rows in data base will still have the &lt;code&gt;NULL&lt;/code&gt; value and it is needed to fill them "by hands" to start to prettify the ruby code. Then you can rely on the presence of the column for every row.&lt;/p&gt;

&lt;p&gt;❗The main point is to maintain compatibility: change the database presets first and ship it. Then cut off the default value initialization out of Ruby-code, when you don't have NULL-values in the column anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not-Null Constraints
&lt;/h2&gt;

&lt;p&gt;Have you ever faced code like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageUploadService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue is similar to the lack of default value. When a column is nullable - web couldn't be sure that there is no &lt;code&gt;nil&lt;/code&gt; value. It enforces to write &lt;code&gt;nil&lt;/code&gt;-checking logic every time we have to use the column.&lt;/p&gt;

&lt;p&gt;If we guarantee that column does not have the &lt;code&gt;nil&lt;/code&gt; value, the code will become much simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the solution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A not-null constraint specifies merely that a column must not assume the null value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create a new table and with not-null column
&lt;/h3&gt;

&lt;p&gt;Set the &lt;code&gt;default&lt;/code&gt; options and the column will have the value set by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:points_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding not-null column to existing table
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://medium.com/klaxit-techblog/zero-downtime-migrations-with-activerecord-47528abe5136" rel="noopener noreferrer"&gt;This article&lt;/a&gt; describes in detail the safe way to add the new column with default, but here are the summary steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill all the null-values in the database with some value&lt;/li&gt;
&lt;li&gt;Set column not-null (by migration)&lt;/li&gt;
&lt;li&gt;Cut off the "in-place" model initialization code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗The better way to fill values of the overloaded table is to update with batches (every batch in its transaction) and to stretch in time.&lt;/p&gt;

&lt;p&gt;The standard way to automate things in Ruby world is &lt;code&gt;Rake&lt;/code&gt;-tasks, and here is the sample of rake-task to update users table users with batches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Fill user points count'&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:migrations&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;fill_user_points_count: :environment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;total_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;updated_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;points_count: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;points_count: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;updated_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero?&lt;/span&gt;

      &lt;span class="n"&gt;total_updated&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;updated_rows&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"updated &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;total_updated&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; users"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend using rake-task instead of updating columns in migration, because of it &lt;br&gt;
gives &lt;strong&gt;more control to run it on production&lt;/strong&gt; (you can interrupt process at any time if something goes wrong). And here is some tricky migration to automate setting the default value for staging environment and the other developer machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FillOrderSourceAndSetNotNull&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Run bundle exec migrations:fill_user_points_count in production"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'migrations:fill_user_points_count'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This migration disables transactional mode for the migration and updates users with batches of 10 000 rows. It allows to not to have the large table lock if the table updates in one transaction.&lt;/p&gt;

&lt;p&gt;After this step - we can easily set table not-null:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;change_column_null&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:points_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and deploy changes to production.&lt;br&gt;
The last step is to cut out nil-checking code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.points_count ||= 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Following this two simple rules: 'set the default value' and 'set column not null' are very useful to write more clear code and avoid many of the problems.&lt;/p&gt;

&lt;p&gt;Thanks for reading! See you at the next topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/10/ddl-constraints.html" rel="noopener noreferrer"&gt;Many details of PorstreSQL constraints in official domentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://leopard.in.ua/2016/09/20/safe-and-unsafe-operations-postgresql#.W-2GDzkSw0M" rel="noopener noreferrer"&gt;Safe and unsafe operations for high volume PostgreSQL&lt;/a&gt; by Alexey Vasiliev&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bignerdranch.com/blog/coding-rails-with-data-integrity/" rel="noopener noreferrer"&gt;Coding Rails with Data Integrity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/klaxit-techblog/zero-downtime-migrations-with-activerecord-47528abe5136" rel="noopener noreferrer"&gt;Zero downtime migrations with ActiveRecord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>database</category>
      <category>dataintegrity</category>
    </item>
    <item>
      <title>Concerns testing with anonymous controller</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Wed, 10 Oct 2018 08:41:48 +0000</pubDate>
      <link>https://forem.com/amplifr/testing-concerns-in-anonymous-controller-2e71</link>
      <guid>https://forem.com/amplifr/testing-concerns-in-anonymous-controller-2e71</guid>
      <description>&lt;p&gt;Once you have a controller concern like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;BillingErrorRescuer&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LimitExceeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :rescue_billing_limits&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rescue_billing_limits&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PlanPresenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is to test is separately in anonymous controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'spec_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;BillingErrorRescuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;BillingErrorRescuer&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;
      &lt;span class="vi"&gt;@account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FactoryBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LimitExceeded&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"anonymous#action"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:action&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;state: :trial&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'renders json with billing data'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match_json_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'controllers/billing_error_rescuer'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does this test do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enables testing controller features in rspec&lt;/li&gt;
&lt;li&gt;Defines anonymous controller to include concern extension&lt;/li&gt;
&lt;li&gt;Draw the route for the action&lt;/li&gt;
&lt;li&gt;Calls the action with the default controller pipeline&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thats it!&lt;br&gt;
Thanks &lt;code&gt;rspec-rails&lt;/code&gt; gem, that we have almost everything out of the box!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>testing</category>
      <category>rspec</category>
    </item>
    <item>
      <title>Stressless pull-request watching with Google Mail</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 02 Oct 2018 17:12:16 +0000</pubDate>
      <link>https://forem.com/amplifr/stressless-pull-request-watching-with-google-mail-2ob9</link>
      <guid>https://forem.com/amplifr/stressless-pull-request-watching-with-google-mail-2ob9</guid>
      <description>&lt;p&gt;Let's share the experience of watching repositories, doing review and reacting to the messages.&lt;/p&gt;

&lt;p&gt;I am going to review some common tips of organising pull-request watching with Google Mail, that work for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not Slack
&lt;/h2&gt;

&lt;p&gt;Slack is integrated merely with GitHub but has no grouping, no priorities. Messages go in a timeline feed, so comments of different pull-requests alternate and intermix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Mail benefits
&lt;/h2&gt;

&lt;p&gt;Google Mail is very smart and helpful in organising email processing and have many features to be productive. We are going review some of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Messages chains
&lt;/h2&gt;

&lt;p&gt;Google Mail groups all the incoming messages into the chains and puts all the GitHub’s comments of one pull-request in one. No matter how many unread messages of one pull-request you have - they all appear as one unread chain (with the number of incomes).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpa60gef46p0m7fqhv4vq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpa60gef46p0m7fqhv4vq.png" alt="Google Mail message chains with the amount of messages count"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗ Chaining breaks after renaming pull-request, and all the new comments with getting in the new group. That is why do not recommend to name pull-request correctly for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labels
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/mail/answer/118708?hl=en&amp;amp;ref_topic=3394656" rel="noopener noreferrer"&gt;Labels&lt;/a&gt; allow organising all the emails in groups. They work like folders, but you can add multiple labels to a message. We will use them to specify the message types, e.g. &lt;em&gt;“For review”&lt;/em&gt;, &lt;em&gt;“My team”&lt;/em&gt;, &lt;em&gt;“OSS”&lt;/em&gt; and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk3imaqj2f9tedl5ohrgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk3imaqj2f9tedl5ohrgd.png" alt="Labelling Google Mail messages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗ Labels are flexible, and you could reconfigure them at any time when you will need to reorganise your flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filters
&lt;/h2&gt;

&lt;p&gt;Google Mail uses &lt;a href="https://support.google.com/mail/answer/6579?hl=en&amp;amp;ref_topic=3394656" rel="noopener noreferrer"&gt;customisable rules&lt;/a&gt; to preprocess incoming messages. This is the excellent way to classify emails and apply the source-specific label to them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9v4yhewv7mujwg753xen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9v4yhewv7mujwg753xen.png" alt="Filters settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritisation
&lt;/h2&gt;

&lt;p&gt;Simple "prioritisation" could be maid manual by arranging labels decreasing the urgency of the messages you have to react.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snoozing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/mail/answer/7622010?co=GENIE.Platform%3DDesktop&amp;amp;hl=en" rel="noopener noreferrer"&gt;Snoozing&lt;/a&gt; is the new feature. It is handy in the context of pull-requests watching because it allows postponing the incoming mail immediately when you are not able to react at the moment.&lt;/p&gt;

&lt;p&gt;Now we are going to tune up those features to improve the organisation of watching and reviewing pull-requests process.&lt;/p&gt;

&lt;h1&gt;
  
  
  Google Mail features in action
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Labels
&lt;/h2&gt;

&lt;p&gt;First of all, we have to think about repositories you are watching and the urgency of incoming data. Here is my sample. You could use it to start and make it more proper to your case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyhizw7sew036lf61dmm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyhizw7sew036lf61dmm8.png" alt="Basic labels for watching pull-requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review&lt;/strong&gt; - holds the pull-requests from where someone is asking my review with the GitHub's "Reviewers" tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt; - is used to put messages from the backend team. Here might be some pull-requests which are in work now (WIP) and probably no review has been requested yet. As I am in the back-end team, it is essential to keep track of events of my team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt; labels used to mark frontend team's pull-requests. It is less important to watch those pull-requests but lets me observe what is happening in the frontend part of Amplifr.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team&lt;/strong&gt;: here are all the pull-request of the all Amplifr working project repositories. Most of the are auxiliary and don't change too often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Used gems&lt;/strong&gt;. At the backend projects, written with Ruby on Rails, we use many Ruby gems. And it is handy to follow all the updates, that is why I watch some repositories like &lt;a href="https://github.com/sferik/twitter" rel="noopener noreferrer"&gt;twitter gem&lt;/a&gt;.&lt;br&gt;
But this updates are not urgent and could be viewed at the free time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OSS&lt;/strong&gt; - this label is for following pull-requests of the open source projects I contribute to or like to track updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GITHUB&lt;/strong&gt; - I use this label to mark all the messages from GitHub to overview the total count.&lt;/p&gt;
&lt;h2&gt;
  
  
  Filters
&lt;/h2&gt;

&lt;p&gt;These filters work for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Matches: from:(notifications@github.com)
Do this: Apply label "GITHUB", Never send it to Spam

Matches: from:(noreply@github.com) subject:(Github Subscribed)
Do this: Mark as read, Delete it

Matches: from:(notifications@github.com) to:(review_requested@noreply.github.com)
Do this: Apply label "1) Review", Mark it as important

Matches: to:(evilmartians/amplifr &amp;lt;amplifr@noreply.github.com&amp;gt;)
Do this: Apply label "2) Backend", Mark it as important

Matches: to:(evilmartians/amplifr-front &amp;lt;amplifr@noreply.github.com&amp;gt;)
Do this: Apply label "3) Frontend"

Matches: (to:(gazay/gon) OR to:(sferik/twitter) OR to:(philnash/bitly) OR :to:(wilddima/logux_rails)
Do this: Apply label "5) Used gems"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❗ &lt;a href="https://gist.github.com/dsalahutdinov/d0d33ef0aaedea4c54071665572d1bd5#file-filters-xml" rel="noopener noreferrer"&gt;You could download the base filters from my Gist&lt;/a&gt;, import them into Google Mail, and start tuning them for your flow.&lt;/p&gt;

&lt;p&gt;After that, we can see all the messages grouped by the urgency of them. A quick view of the label's table let you see how many tasks are pending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Mail Checker
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jasonsavard.com/ru-RU/Checker-Plus-for-Gmail" rel="noopener noreferrer"&gt;"GMail Checker"&lt;/a&gt; is the excellent browser plugin for checking the mail and for getting notified about new messages.&lt;/p&gt;

&lt;p&gt;The main features, I use, are:&lt;/p&gt;

&lt;p&gt;1) "Do not disturb" for the full-screen mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3pg92niya9ylqz9zg2ej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3pg92niya9ylqz9zg2ej.png" alt="DnD on full-screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This option is beneficial not to lose concentration when you are coding in the VIM or browsing something in full screen.&lt;/p&gt;

&lt;p&gt;2) Notify for the specified labels email:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2nbq00crnzhdhdvnxr08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2nbq00crnzhdhdvnxr08.png" alt="Notify the most critical label's email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature lets me react fast for the most crucial messages.&lt;/p&gt;

&lt;p&gt;The mail checker also has many features, which would be very useful to improve usage of the mail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mental setup
&lt;/h2&gt;

&lt;p&gt;When automation works well, you only need to view comments starting from to most urgent and react to them. And here is the most critical part for human factor come out.&lt;/p&gt;

&lt;p&gt;Imaging the cause, you open the new email, read the comments, go to the GitHub, and then you look aside and switch to other activity, forgetting about this message. In this case, you miss the sight because you have the letter read in Google Mail but without no reaction.&lt;/p&gt;

&lt;p&gt;❗The primary mental setup - is to review incoming mail intently and always make a reaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;directly, with approving pull-request, doing the review or commenting&lt;/li&gt;
&lt;li&gt;implicitly, with snoozing the message (for the more suitable time)&lt;/li&gt;
&lt;li&gt;or with marking the email "unread" to leave it for the next session, you will work on the email.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Wrap up
&lt;/h1&gt;

&lt;p&gt;Spending one hour for organising incoming emails let to save more time in the future, It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;makes email browsing smarter by using prioritisation and labels&lt;/li&gt;
&lt;li&gt;shorten the reaction time for the most urgent messages&lt;/li&gt;
&lt;li&gt;gives the flexibility - all the settings could be changed at any time for any situation to perform maximum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>github</category>
      <category>review</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to migrate monolith to the scary new version of Rails</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 25 Sep 2018 19:14:08 +0000</pubDate>
      <link>https://forem.com/amplifr/how-to-migrate-monolith-to-the-scary-new-version-of-rails-3o52</link>
      <guid>https://forem.com/amplifr/how-to-migrate-monolith-to-the-scary-new-version-of-rails-3o52</guid>
      <description>&lt;p&gt;Migration to the new version of Rails always looks scary. Thinking about the upgrade raises many questions, especially if your application is an older monolith. The most critical points are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upgrading without blocking feature development&lt;/li&gt;
&lt;li&gt;affecting as fewer users as possible&lt;/li&gt;
&lt;li&gt;making sure all logic works correctly on the new version of Rails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we will review some common techniques and tips which let Amplifr’s team have the painless incremental upgrade to the latest version of Rails.&lt;/p&gt;

&lt;p&gt;❗The main tip - is to &lt;strong&gt;upgrade incrementally only to the &lt;a href="https://rubygems.org/gems/rails/versions"&gt;latest patch version&lt;/a&gt; of Rails at once&lt;/strong&gt;, because most of the bugs have already been solved. And this choice minimises breaking changes you need to deal with each upgrade.&lt;/p&gt;

&lt;p&gt;We had done three upgrades steps: &lt;code&gt;4.2.0 =&amp;gt; 5.0.7&lt;/code&gt; , &lt;code&gt;5.0.7 =&amp;gt; 5.1.6&lt;/code&gt;, &lt;code&gt;5.1.6 =&amp;gt; 5.2.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here are the standard steps of one Rails version upgrade cycle and delivering it to production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;deprecations:&lt;/strong&gt; clean up all deprecations, which block the next Rails version update&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dual boot:&lt;/strong&gt; make application runnable with current and next version of Rails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;green tests:&lt;/strong&gt; make tests pass for both Rails (matrix build)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deploy → monitor → (revert) → fix:&lt;/strong&gt; switch to the new version of Rails, monitor application for the new failures and bugs, revert if bugs are critical and you need time to fix them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;keep doing&lt;/strong&gt; point number four until everything is ok&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeat until you are on the top Rails version 😁 &lt;/p&gt;

&lt;h1&gt;
  
  
  Deprecations
&lt;/h1&gt;

&lt;p&gt;Deprecations say that something can break when a third party dependency gets updated. As we are going to upgrade Rails - we have to fix the blockers for the next version of Rails.&lt;/p&gt;

&lt;p&gt;The main problem is to separate the target version blockers from fixing them first. Hiding the future version deprecations by using this dangerous and ugly-hack worked for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="c1"&gt;# temporary mute test params deprecations&lt;/span&gt;
      &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Deprecation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;behavior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/Using positional arguments in functional tests has been deprecated/&lt;/span&gt;
          &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Deprecation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_BEHAVIORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:stderr&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❗&lt;strong&gt;It is better to fix all deprecations at once&lt;/strong&gt; if it’s possible, it will save a lot of time in the future&lt;/p&gt;

&lt;h1&gt;
  
  
  Dual boot
&lt;/h1&gt;

&lt;p&gt;The idea is to support both versions of Rails at the same time. Keeping application runnable on current Rails version guarantees that you will be able to run the application on it if something goes wrong. Making application runnable on the next Rails version will allow you to have continuous upgrade.&lt;/p&gt;

&lt;p&gt;Make your application able to run with both versions of Rails depending on environment variable &lt;code&gt;RAILS_NEXT&lt;/code&gt;:&lt;br&gt;
This command should boot current Rails version&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this will boot application with the new version of Rails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nv"&gt;RAILS_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The way to do it - is to hack &lt;code&gt;bundler&lt;/code&gt; a little bit by putting this code at the beginning of &lt;code&gt;Gemfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Bundler::SharedHelpers&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_lockfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@default_lockfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_lockfile&lt;/span&gt;
    &lt;span class="vi"&gt;@default_lockfile&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;default_gemfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.lock"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Kernel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_next?&lt;/span&gt;
    &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"RAILS_NEXT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rails_next?&lt;/span&gt;
  &lt;span class="no"&gt;Bundler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SharedHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_lockfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Bundler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SharedHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_gemfile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_next.lock"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bundler::Dsl&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nb"&gt;method_defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_definition_unpatched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:to_definition_unpatched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                   &lt;span class="ss"&gt;:to_definition&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bad_lockfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;to_definition_unpatched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Bundler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SharedHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_lockfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt; &lt;span class="c1"&gt;#relax dependency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code does a couple of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines the &lt;code&gt;rails_next&lt;/code&gt;? method in &lt;code&gt;Kernel&lt;/code&gt; class, so this method is available to use everywhere: in &lt;code&gt;Gemfile&lt;/code&gt;, in your ruby application’s code and so on&lt;/li&gt;
&lt;li&gt;Defines singleton-method to hold the current bundler’s lock-file name&lt;/li&gt;
&lt;li&gt;Reset the lock-file name to &lt;code&gt;Gemfile_next.lock&lt;/code&gt; if &lt;code&gt;rails_next?&lt;/code&gt; is activated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗Note, that we &lt;strong&gt;use one &lt;code&gt;Gemfile&lt;/code&gt; and two lock-files for every version of Rails&lt;/strong&gt;. It is much easier to maintain.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dependencies
&lt;/h1&gt;

&lt;p&gt;First of all, relax Rails dependencies in &lt;code&gt;Gemfile&lt;/code&gt; to enable higher versions of Rails:&lt;br&gt;
If you have rails version locked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 4.2.3'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlock it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It allows &lt;code&gt;Gemfile&lt;/code&gt; to be compatible with all versions of Rails. Current and next rails version will be locked in the corresponding &lt;code&gt;lock&lt;/code&gt; -files.&lt;/p&gt;

&lt;p&gt;Then copy &lt;code&gt;Gemfile.lock&lt;/code&gt; to &lt;code&gt;Gemfile_next.lock&lt;/code&gt; and try to update rails on &lt;code&gt;rails_next&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;Gemfile.lock Gemfile_next.lock
    &lt;span class="c"&gt;# run bundle for fixing dependencies matedata in original Gemfile&lt;/span&gt;
    &lt;span class="nv"&gt;$ &lt;/span&gt;bundle
    &lt;span class="c"&gt;# run bundle on rails_next to update rails&lt;/span&gt;
    &lt;span class="nv"&gt;$ RAILS_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle update rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you will get bundler working hard to resolve dependencies and probably fail in the middle. Failure means that some of the dependencies are not allowed to work with the newer version of Rails.&lt;br&gt;
Here are some scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gem has current version locked and not supported. And it has the newer version to support the newer version of Rails. We have to update gem if only the &lt;code&gt;rails_next?&lt;/code&gt; works:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rails_next?&lt;/span&gt;
      &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'devise'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'devise'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;git: &lt;/span&gt;&lt;span class="s1"&gt;'https://github.com/plataformatec/devise'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;branch: &lt;/span&gt;&lt;span class="s1"&gt;'3-stable'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Gem has Rails version locked from above: &lt;code&gt;rails &amp;lt; 5.0&lt;/code&gt; but probably works with newer version. You need to unlock dependencies and give it a try.
Try to clone this gem from GitHub locally, relax dependency of Rails in &lt;code&gt;gemspec&lt;/code&gt; file and plug gem locally. If it works - great, move to the next blocker, if not - try other solutions.&lt;/li&gt;
&lt;li&gt;Gem has Rails locked, because of incompatibility and does not work with newer version. You need to unlock dependencies, understand how gem works and where is the problem, then fix all the conflicts and enable tests for the new version of rails in this gem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dealing with lasts cases - you will have to update gem source code. First of all, you will fix the problem in your fork. Despite this way looks very simple, fast and easy - there are many reasons for avoiding keeping it in forks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authors (or a group of authors) maintain their gems in a centralised way. Nobody is interested in supporting your fork 😢  Your fork is your problem!&lt;/li&gt;
&lt;li&gt;Forking let you fix the problem fast, without design work and tests, but it is just postponement of the problem. You will pay back when upgrading the fork from the remote source in future&lt;/li&gt;
&lt;li&gt;Fix a problem in a centralised way (by sending pull-request) helps other developers to deal with the same problem. Feel free to contribute by submitting the pull-request!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗The better way to fix gem’s source code - is to &lt;strong&gt;send pull-request to the gem’s repository&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Unfortunately, some gems do not have anybody to maintain because of many reasons. And this is the signal for you to inspect your dependencies and get rid of unmaintainable ones in favour of more popular.&lt;/p&gt;

&lt;p&gt;❗Note that you have to &lt;strong&gt;update all gems twice to support two version of Rails&lt;/strong&gt;, and commit both &lt;code&gt;Gemfile.lock&lt;/code&gt; and &lt;code&gt;Gemfile_next.lock&lt;/code&gt; to keep them similar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    bundle update some-gem
    &lt;span class="nv"&gt;RAILS_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle update some-gem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Runnable app
&lt;/h1&gt;

&lt;p&gt;When all dependencies are okay, and your bundler successfully resolved for the &lt;code&gt;rails_next&lt;/code&gt; - you are ready to proceed to the next step - check if the application is runnable.&lt;/p&gt;

&lt;p&gt;You could try to run Rails console or local web-server, but it would be better to start from running the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nv"&gt;RAILS_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a high probability tests would not start or even fail. The main problem here is the incompatibilities of your code or some gems’ code with new Rails version. So only one tip: go deeper and fix it.&lt;/p&gt;

&lt;p&gt;❗ One trick we used - is to write &lt;strong&gt;Rails version dependent code using &lt;code&gt;rails_next?&lt;/code&gt; method temporarily&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:commit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rails_next?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It helps to write the version-specific code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rails_next?&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;user_omniauth_authorize_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:instagram&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;user_instagram_omniauth_authorize_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Green tests
&lt;/h1&gt;

&lt;p&gt;Add the extra build to you CI for running tests with the next Rails version. If you are running on TravisCI, it might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RAILS_NEXT=0&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RAILS_NEXT=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your next Rails version tests not pass yet - you can mark them optional like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RAILS_NEXT=0&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RAILS_NEXT=1&lt;/span&gt;
      &lt;span class="na"&gt;allow_failures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RAILS_NEXT=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach will let you merge matrix-build to the main branch without affecting feature-development, and you’ll be able to work on tests in parallel.&lt;/p&gt;

&lt;p&gt;Finally, you need to make your tests green for both versions.&lt;/p&gt;

&lt;p&gt;❗&lt;strong&gt;Getting upgrade without having tests - is not a very good idea&lt;/strong&gt; 🙂, because it’s impossible to ensure crucial application parts work on the next version.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy &amp;amp; Monitor
&lt;/h1&gt;

&lt;p&gt;It time to rollout!&lt;/p&gt;

&lt;p&gt;If you have staging - deploy there first and check the main functionality by hands. It minimises risks and may give a chance to catch some bugs.&lt;/p&gt;

&lt;p&gt;Running in production is more effective. It makes the real users test application within the reals cases. The main idea is to deploy the new Rails version by “a little bit” and keeping in mind revert strategy, that would help you not to affect users too much or too long.&lt;/p&gt;

&lt;p&gt;Depending on your infrastructure you may prefer to use one of the rollout strategies, or combine them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;serving specific endpoints with new Rails version application instance, starting from low-loaded to high-loaded, or starting from non-critical.&lt;/li&gt;
&lt;li&gt;serving the percentage of overall load, and then run to increase the rate.&lt;/li&gt;
&lt;li&gt;rollout the application at the low-loaded time (if you have those), e.g. at night-time&lt;/li&gt;
&lt;li&gt;rollout 100% and keep calm 🙂 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We decided to roll out the all Amplifr with new Rails version, because it is the most progressive way, and rolling back to the previous Rails version took a couple of minutes for us.&lt;/p&gt;

&lt;p&gt;Once you’ve rolled out the application (or it’s part) and let real users face it - it is essential to keep monitor errors.&lt;br&gt;
There are two general ways to get to know about problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;automatic monitor with some tools like &lt;code&gt;Honeybadger&lt;/code&gt;, &lt;code&gt;Rollbar&lt;/code&gt;, &lt;code&gt;Sentry&lt;/code&gt;, &lt;code&gt;NewRelic&lt;/code&gt;. &lt;strong&gt;Having it - is a must!&lt;/strong&gt; They are useful to be notified about causes of issues and bugs online with Slack, email or other ways&lt;/li&gt;
&lt;li&gt;real users are critically helpful for small teams like Amplifr. No matter how much you’ve tested the application and how high test-coverage you have – it’s likely that you missed few bugs, especially in business logic.  Always be kind and patient with your users, and they will behave the same way when you need their help.🙂&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If something goes wrong - revert by changing the value of &lt;code&gt;RAILS_NEXT&lt;/code&gt;  environment variable. The exact way depends on the infrastructure you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RAILS_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❗&lt;strong&gt;Rolling back does not mean failure&lt;/strong&gt;, so do not worry. It gives you the time to fix newly found errors while the application runs stable on the previous version of Rails. &lt;strong&gt;Just keep it up!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have several rollbacks for fixing the critical bugs in Amplifr.&lt;/p&gt;

&lt;p&gt;❗After the successful upgrade, let the application to work on the new version of Rails for a long-term to minimise risks and fix all the bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;After the successful upgrade, we have clean up the source code and cut off the support of the previous Rails.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the application run with the new version of Rails by default. You have to copy next &lt;code&gt;lock&lt;/code&gt;-file content and commit it:
cat Gemfile_next.lock &amp;gt; Gemfile.lock
git commit ...&lt;/li&gt;
&lt;li&gt;Deploy the application and switch RAILS_NEXT &lt;code&gt;1&lt;/code&gt; → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cut off the &lt;code&gt;Gemfile_next.lock&lt;/code&gt;:
  git rm Gemfile_next.lock
  git commit&lt;/li&gt;
&lt;li&gt;Remove all the version dependent code; you can easily find it:
grep -ir "rails_next\?" ./&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;Bunder&lt;/code&gt;'s hacks from the &lt;code&gt;Gemfile&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We haven’t removed the dual boot code and kept running on the non-updated &lt;code&gt;lock&lt;/code&gt;-file for a couple of days. 🤦‍♂️ Be careful with it. 🙂 &lt;/p&gt;

&lt;p&gt;❗If you are going to keep upgrading - run from the begging with the next version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay on edge
&lt;/h2&gt;

&lt;p&gt;If you are on the last stable version, you might prefer to stop on it. Another option is to keep upgrading with the &lt;code&gt;master&lt;/code&gt; branch as next version of Rails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;git: &lt;/span&gt;&lt;span class="s1"&gt;'git@github.com/rails/rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way is not single-valued but probably is the progressive one. Most of the gems do not support the newer version, and you will have to contribute there. It could be dangerous and make you catch bugs of the unstable features in Rails code.&lt;br&gt;
But from the other hand - you will keep on the wave with the new version of Rails could make your upgrade painless and comfortable in the future, and be helpful by contributing to Ruby’s gems source codes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Although upgrade Rails version might look like a tremendous amount of work, it is essential to be on the latest version, because of this reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is more stable, fast&lt;/li&gt;
&lt;li&gt;it has new features&lt;/li&gt;
&lt;li&gt;it lets you use the latest versions of gem’s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing upgrades iteratively, step by step, and keeping in mind revert strategy can make your upgrade less painful.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further reading(watching):
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.recursion.org/incremental-rails-upgrade/"&gt;Upgrading a Rails application incrementally&lt;/a&gt; by Luke Francl&lt;/li&gt;
&lt;li&gt;RailsConf 2017: &lt;a href="https://www.youtube.com/watch?v=I-2Xy3RS1ns"&gt;Upgrading a big application to Rails 5&lt;/a&gt; by Rafael França&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://engineering.shopify.com/blogs/engineering/upgrading-shopify-to-rails-5-0"&gt;Upgrading Shopify to Rails 5&lt;/a&gt; at the Shopify's Blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://engineering.shopify.com/blogs/engineering/introducing-the-deprecation-toolkit"&gt;Shopify's Deprecation Toolkit use cases&lt;/a&gt; at the Shopify's Blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://speakerdeck.com/jnraine/ten-years-of-rails-upgrades"&gt;Ten Years of Rails Upgrades&lt;/a&gt; slides by Jordan Raine&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bytes.babbel.com/en/articles/2015-12-14-rails-4-upgrade.html"&gt;How we upgraded a big Ruby on Rails monolith with Zero Downtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nqGxzUGtkNw"&gt;Kir Shatrov's talk about Rails upgrade at Shopify (russian language)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>upgrade</category>
    </item>
  </channel>
</rss>
