<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tech notes</title>
    <description>Some random notes about DevOps, SRE, Linux and other stuff. Rus/Eng.</description>
    <link>https://blog.strangeman.info/</link>
    <atom:link href="https://blog.strangeman.info/sitemap.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 03 Apr 2026 09:44:05 +0000</pubDate>
    <lastBuildDate>Fri, 03 Apr 2026 09:44:05 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>How to Hide Modal App Behind Cloudflare Zero Trust</title>
        <description>&lt;h2 id=&quot;why-you-might-need-this&quot;&gt;Why you might need this&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;To remove your app from public access without implementing auth in code&lt;/li&gt;
  &lt;li&gt;To integrate the app with your company’s existing access model&lt;/li&gt;
  &lt;li&gt;To use Google (or another provider) auth on the cheap&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;downsides&quot;&gt;Downsides&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;This is not suitable for production or high load, since cloudflared isn’t designed for heavy traffic and will become a bottleneck&lt;/li&gt;
  &lt;li&gt;Latency will increase, as traffic will follow the additional hops: Cloudflare Edge Servers → Your cloudflared installation → Modal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;You have a Team or Enterprise plan on Modal (required for custom domain support)&lt;/li&gt;
  &lt;li&gt;It’s assumed you have cloudflared deployed and everything set up in Cloudflare Zero Trust and you’re familiar with Cloudflare Zero Trust concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;algorithm&quot;&gt;Algorithm&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Attach your custom domain to Modal and verify it (&lt;a href=&quot;https://modal.com/docs/guide/webhook-urls#custom-domains&quot;&gt;https://modal.com/docs/guide/webhook-urls#custom-domains&lt;/a&gt;). This is needed so Modal’s infrastructure “sees” your domain and you can use it as an entry point.&lt;/li&gt;
  &lt;li&gt;Deploy the app with the custom domain and make sure everything works (without auth for now).&lt;/li&gt;
  &lt;li&gt;Create a Cloudflare Zero Trust Application: specify access policies, auth method, and the app domain (same as your custom domain), then bind it to the tunnel.&lt;/li&gt;
  &lt;li&gt;In the tunnel settings, add a route from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your_custom_domain&lt;/code&gt; to Modal’s standard web endpoint (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&amp;lt;source&amp;gt;--&amp;lt;label&amp;gt;.modal.run&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;If your app’s cold start is fairly long, consider bumping the Connect Timeout in the route settings from the default 30 seconds.&lt;/li&gt;
  &lt;li&gt;In your DNS provider, change the CNAME record from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your_custom_domain: cname.modal.domains&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your_custom_domain: cf_tunnel_id.cfargotunnel.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After this, your app will be accessible via the standard URL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&amp;lt;source&amp;gt;--&amp;lt;label&amp;gt;.modal.run&lt;/code&gt; without auth, and via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://your_custom_domain&lt;/code&gt; with configured auth policies. To disable access through the standard URL, you can add a redirect in the app code. Check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host&lt;/code&gt; HTTP header, and if it doesn’t match &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your_custom_domain&lt;/code&gt;, then redirect to it. You can also handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modal serve&lt;/code&gt; development mode there.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;CUSTOM_DOMAIN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MODAL_CUSTOM_DOMAIN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;modal-demo.your-domain.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asgi_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;custom_domains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CUSTOM_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;web&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FastAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Demo (Modal)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Redirect to custom domain if configured.
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# Skip redirect for ephemeral apps (modal serve) — their hosts end with &quot;-dev.modal.run&quot;.
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;_redirect_enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CUSTOM_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_redirect_enabled&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEADERS_DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;_allowed_hosts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CUSTOM_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_redirect_enabled&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

            &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_domain_middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call_next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_redirect_enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;host&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;is_ephemeral&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-dev.modal.run&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_ephemeral&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_allowed_hosts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;scheme&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CUSTOM_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RedirectResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;301&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call_next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;I don’t have exact knowledge of Modal’s internal infrastructure, so this is a hypothesis that may contain inaccuracies.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When we create a Modal app with custom_domains attached, Modal’s infrastructure gets instructed that incoming traffic with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host: your_custom_domain&lt;/code&gt; header should be routed to our app. With the default DNS settings &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your_custom_domain: cname.modal.domains&lt;/code&gt;, this follows the route &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browser → cname.modal.domains → app&lt;/code&gt;. If we change the DNS record, the route gets longer: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browser → Cloudflare network → our cloudflared installation → app&lt;/code&gt;, but the host header stays the same, so Modal’s infrastructure still routes traffic to the correct app.&lt;/p&gt;
</description>
        <pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/notes/devops/2026/04/03/modal-cloudflare-zero-trust.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/notes/devops/2026/04/03/modal-cloudflare-zero-trust.html</guid>
        
        
        <category>notes</category>
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>CI/CD for Deploying Flows in Prefect Cloud 2</title>
        <description>&lt;h2 id=&quot;initial-requirements&quot;&gt;Initial Requirements&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;We’ve got a bunch of Python scripts representing various Flows, all packed in one repository.&lt;/li&gt;
  &lt;li&gt;The goal is to deploy the same Flow in the form of multiple Deployments (with different parameters and schedules).&lt;/li&gt;
  &lt;li&gt;Also, we need to deploy the Flows in two different environments (Dev and Prod) for testing and debugging.&lt;/li&gt;
  &lt;li&gt;We prefer defining everything in code rather than messing around with manual configurations in a web interface&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-prefect-offers&quot;&gt;What Prefect Offers&lt;/h2&gt;

&lt;p&gt;The Prefect documentation (&lt;a href=&quot;https://docs.prefect.io/2.14.3/&quot;&gt;https://docs.prefect.io/2.14.3/&lt;/a&gt;) is a bit confusing, but after some trial and error, here’s what I found out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Prefect gives us two methods to execute Flows - using agents and workers (&lt;a href=&quot;https://docs.prefect.io/2.14.3/guides/upgrade-guide-agents-to-workers/&quot;&gt;https://docs.prefect.io/2.14.3/guides/upgrade-guide-agents-to-workers/&lt;/a&gt;). The difference, as far as I can tell, lies in setting up the underlying infrastructure. With agents, you configure it in the infrastructure block (&lt;a href=&quot;https://docs.prefect.io/2.14.3/concepts/infrastructure/&quot;&gt;https://docs.prefect.io/2.14.3/concepts/infrastructure/&lt;/a&gt;), and for workers, it’s done through job templates. We opted for agent-based deployments to avoid tweaking our Flows too much.&lt;/li&gt;
  &lt;li&gt;There are three main ways to deploy our Deployments:
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect deployment build&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect deployment apply&lt;/code&gt; - this lets you set parameters for each Deployment individually. It’s an a bit more legacy command that supports both agent and worker configurations.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect deploy&lt;/code&gt; - it can read the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect.yaml&lt;/code&gt; config file and deploy everything from there, or you can specify parameters for a single deployment, similar to the previous method. This is a newer approach that only works with workers and doesn’t support things like infrastructure blocks. Plus, it has a limitation - it can deploy either a single flow or all the flows listed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect.yaml&lt;/code&gt;, and you can’t have multiple configs like that in one project.&lt;/li&gt;
      &lt;li&gt;Call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serve&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;to_deployment&lt;/code&gt; functions directly from your Flow code.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;description-of-deployment-infrastructure&quot;&gt;Description of Deployment Infrastructure&lt;/h2&gt;

&lt;p&gt;To keep things simple, we decided to skip storage blocks and store all Flows directly in the Docker image of the agent. This image is also used for CI:&lt;/p&gt;

&lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# change to exact version if needed, e.g. 2.8.6-python3.10&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# see https://hub.docker.com/r/prefecthq/prefect/tags?page=1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; prefecthq/prefect:2-python3.10&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;/usr/local/bin/python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; pip

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /opt/prefect&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PYTHONPATH &quot;${PYTHONPATH}:/opt/prefect/&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PREFECT_QUEUE &quot;default&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; requirements.txt /opt/prefect/requirements.txt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; requirements.txt
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;prefect block register &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; prefect_aws.ecs

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; src /opt/prefect/src/&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; flows /opt/prefect/flows/&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# default CMD for agent&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# workers will be spawned with their own cmd&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [ &quot;sh&quot;, &quot;-c&quot;, &quot;prefect agent  start -q ${PREFECT_QUEUE}&quot; ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For Deployments, we chose the first deployment method - using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect deployment build&lt;/code&gt;. We wanted to continue defining the infrastructure alongside the Flow in code (as infrastructure blocks). The method of “deploy everything from the project” didn’t suit us because we needed to deploy flows in two different environments in different stages of development.&lt;/p&gt;

&lt;p&gt;However, the idea of having a config file describing all deployments was quite handy. So, we came up with our own wrapper that reads our YAML config specifying deployments for a specific environment.&lt;/p&gt;

&lt;p&gt;Here’s the script we came up with:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;yaml&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sys&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;subprocess&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt;  &lt;span class=&quot;nn&quot;&gt;shlex&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_yaml_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;r&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;safe_load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;yaml_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;FileNotFoundError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error: File &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos; not found.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# construct_prefect_command accepts two parameters:
# deployment - dict with deployment parameters (see examples in prefect-deployments-dev.yaml)
# ib_block - infrastructure block with AWS creds and ECS config (located in the infrastructure folder)
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;construct_prefect_command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ib_block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# mandatory fields
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;entrypoint&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&apos;entrypoint&apos; field is mandatory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&apos;name&apos; field is mandatory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prefect deployment build &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;entrypoint&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; -n &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; -a --skip-upload -ib &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ib_block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# optional fields
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;parameters&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --params=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dumps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;parameters&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tags&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --tag=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cron&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --cron=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;schedule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;cron&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;interval&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --interval=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;schedule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;interval&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rrule&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --rrule=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;schedule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;rrule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;timezone&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --timezone=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;schedule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;timezone&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;anchor-date&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;schedule&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --anchor-date=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;schedule&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;anchor-date&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;work_pool&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;work_pool&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --pool=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;work_pool&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;work_queue_name&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;work_pool&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; --work-queue=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;work_pool&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;work_queue_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;OSError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Script accepts exactly one argument: path to deployments YAML config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_yaml_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ib-block&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&apos;ib-block&apos; field is mandatory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;deployments&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&apos;deployments&apos; field is mandatory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;deployments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;deploy_command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;construct_prefect_command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yaml_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ib-block&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;run &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy_command&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;subprocess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy_command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universal_newlines&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The logic is straightforward: parse the YAML file, check its structure, and convert its fields into parameters for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefect deployment build&lt;/code&gt; command. The YAML file has the following structure:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;ib-block&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ecs-task/dev&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Mandatory field. Credentials and parameters for ECS from the infrastructure folder&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;deployments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Flow1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Mandatory field. Deployment name&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;entrypoint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;examples/hello_world.py:hello&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Mandatory field. The path to the .py file containing the flow you want to deploy (relative to the root directory of your project) combined with the name of the flow function.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Optional field. Dict with parameters for flow&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;42&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Do&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;panic!&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env.stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dev&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Optional field. Scheduler parameters, &quot;cron&quot;, &quot;interval&quot; and &quot;rrule&quot; are supported, see https://docs.prefect.io/2.14.3/concepts/schedules/?h=schedules#schedule-types&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;timezone&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;America/Chicago&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Optional field. List of tags for deployment&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dev&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;some-tag1&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0.0.1&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Optional field. Deployment version&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;work_pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Optional fields. Pool and Work Queue names&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pool&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;work_queue_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dev&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Flow2&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;&amp;lt;...&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All of this is deployed using Github Actions (some code parts are omitted as they are not relevant to the topic):&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to dev&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dev&lt;/span&gt; 

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build-image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build Prefect docker image&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;outputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ steps.docker_image.outputs.IMAGE }}&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check out code&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build, tag, and push docker image&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker_image&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;REGISTRY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;XXXXX&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;REPOSITORY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;prefect2-worker&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;IMAGE_TAG&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dev&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo &quot;IMAGE=${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Prefect Blocks Upload&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-20.04&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# need for glibc compatibility with prefect docker container&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build-image&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;${{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;needs.build-image.outputs.image&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# run in prefect container from the first step&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PREFECT_API_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.PREFECT_API_KEY }}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORKSPACE_NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;example&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PREFECT_IMAGE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;${{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;needs.build-image.outputs.image&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Authenticate to Prefect Cloud and upload block&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;prefect config set PREFECT_API_KEY=${{ secrets.PREFECT_API_KEY }} &lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;prefect config set PREFECT_API_URL=${{ secrets.PREFECT_API_URL }}&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;python3 infrastructure/ecs-task-dev.py # upload infrastructure block to prefect cloud&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Blocks creation finished&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &quot;ECS block built at $(date +&apos;%Y-%m-%dT%H:%M:%S&apos;)&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;deploy-flows&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;build-image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;${{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;needs.build-image.outputs.image&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# run in built prefect container from the first step&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Authenticate to Prefect Cloud&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;prefect config set PREFECT_API_KEY=${{ secrets.PREFECT_API_KEY }} &lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;prefect config set PREFECT_API_URL=${{ secrets.PREFECT_API_URL }}&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy flows to Cloud&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;python upload_deployments.py prefect-deployments-dev.yaml&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo &quot;Flows from prefect-deployments-dev.yaml deployed to Prefect Cloud (dev)&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This solution enabled us to implement the necessary functionality without making any changes to the Flow code. Moreover, since the deployment script is written in Python and is very straightforward, we were able to hand it over to the Data team. This allows them to modify it in the future to better suit their needs.&lt;/p&gt;
</description>
        <pubDate>Thu, 09 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/2023/11/09/prefect-cloud-deployment.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/2023/11/09/prefect-cloud-deployment.html</guid>
        
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>Как сдампить существующую инфраструктуру в AWS в виде кода</title>
        <description>&lt;h2 id=&quot;вводная&quot;&gt;Вводная&lt;/h2&gt;

&lt;p&gt;💡 Интерфейс AWS придуман инопланетянами для опытов над людьми, поэтому понять, какие сущности у нас там есть - довольно сложно. Гораздо проще сдампить всю информацию в виде инфраструктурного кода и ревьюить её уже там.&lt;/p&gt;

&lt;p&gt;Вообще, на рынке довольно много подобных утилит (&lt;a href=&quot;https://www.reddit.com/r/aws/comments/shb053/is_there_a_good_tool_to_map_out_aws/&quot;&gt;https://www.reddit.com/r/aws/comments/shb053/is_there_a_good_tool_to_map_out_aws/&lt;/a&gt; - вот тут например перечисляется большое число сервисов), но часть из них подзаброшены, часть идут за деньги, а часть используют managed подход, а давать креды от AWS каким-то дядям не очень хочется.&lt;/p&gt;

&lt;p&gt;Так что для дампа информации об инфраструктуре мы будем использовать &lt;a href=&quot;https://github.com/GoogleCloudPlatform/terraformer&quot;&gt;https://github.com/GoogleCloudPlatform/terraformer&lt;/a&gt; - утилиту, сделанную инженерами из Google, что обеспечивает довольно высокий уровень поддержки и доверия к ней. Стандартные средства AWS тоже бы подошли, думаю, но у нас инфра не только в AWS, так что хочется универсальности.&lt;/p&gt;

&lt;p&gt;В документации написано, что последняя версия поддерживает Terraform 0.13, но у меня всё отлично завелось и на свежих версиях Terraform (1.2.x и 1.3.x на момент написания статьи).&lt;/p&gt;

&lt;h2 id=&quot;алгоритм&quot;&gt;Алгоритм&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Ставим Terraform (рекомендую это делать через &lt;a href=&quot;https://github.com/tfutils/tfenv&quot;&gt;https://github.com/tfutils/tfenv&lt;/a&gt;, чтобы легко переключаться между версиями)&lt;/li&gt;
  &lt;li&gt;Ставим Terraformer&lt;/li&gt;
  &lt;li&gt;Сетапим доступ до AWS (процесс аналогичен сетапу CLI-утилиты aws, Terraform возьмет креды из &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/credentials&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Создаем необходимую структуру директорий и качаем AWS provider для Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;terraformer
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;terraformer
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;provider &quot;aws&quot; {}&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; main.tf
terraform init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;В результате в директории создадутся все необходимые для работы файлы (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.terraform&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.terraform.lock.hcl&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Запускаем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraformer&lt;/code&gt; обнюхивать нашу инфраструктуру&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraformer import aws &lt;span class=&quot;nt&quot;&gt;--resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--regions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;us-east-1  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; aws &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;output&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Коротко по опциям (если что всё есть в доке - &lt;a href=&quot;https://github.com/GoogleCloudPlatform/terraformer#readme&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/GoogleCloudPlatform/terraformer#readme&quot;&gt;https://github.com/GoogleCloudPlatform/terraformer#readme&lt;/a&gt;)
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import aws&lt;/code&gt; - импортируй в код инфру из AWS. Помимо Амазона поддерживается несколько десятков других провайдеров, в том числе и CloudFlare&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--resources=*&lt;/code&gt; - импортируй все ресурсы. Можно сдампить только определенный тип ресурсов, если нужно будет аудитить только какие-то конкретные штуки например&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--regions=us-east-2&lt;/code&gt; - импортируй данные только с этого региона. По дефолту сдампит со всех регионов. Глобальные сущности (IAM, пользователи например) сдампятся в любом случае&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-o aws&lt;/code&gt; - в какую директорию дампить&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p {output}/&lt;/code&gt; - какую структуру директорий создавать. По умолчанию используется паттерн &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{output}/{provider}/{service}/&lt;/code&gt; (т.е. код раскладывается по разным директориям в зависимости от сервиса), что в моем случае оказалось не очень удобно&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ну и всё, через какое-то время он вываливает всё описание куда ему сказали и можно начинать ревьюить. В результате выплёвывается как код Terraform (файлы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tf&lt;/code&gt;), так и стейт (файлы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tfstate&lt;/code&gt;), что удобно.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;известные-проблемы&quot;&gt;Известные проблемы&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;С поддержкой AWS всё хорошо, но у CloudFlare не поддерживаются Cloudflare Pages например&lt;/li&gt;
  &lt;li&gt;Время от времени вываливает какие-то ошибки, на описании инфраструктуры это не очень сказалось, но скорее всего код для использования в Terraform надо будет допиливать руками&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/2023/01/30/terraformer-guide.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/2023/01/30/terraformer-guide.html</guid>
        
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>Тулинг для миграции уже созданной инфраструктуры в Terraform</title>
        <description>&lt;p&gt;Потратил тут довольно много времени на задачу по перетаскиванию когда-то созданной руками инфры под Terraform. Инфраструктура естественно уже в продакшене, на нее идёт трафик, так что метод “снести всё нафиг и построить новое светлое вечное” не подходил, пришлось менять колёса в автомобиле на ходу.
Сильно на алгоритмах переноса останавливаться не буду, всё таки тут всё сильно зависит от конкретной инфры, просто опишу тулинг, который мне в этом помог.&lt;/p&gt;

&lt;h2 id=&quot;terraform-import&quot;&gt;&lt;a href=&quot;https://learn.hashicorp.com/tutorials/terraform/state-import&quot;&gt;terraform import&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Про него рассказывать особо нечего, это часть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform&lt;/code&gt;, которая позволяет “обнюхать” уже имеющийся ресурс и положить его в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfstate&lt;/code&gt;. Проблема в том, что этого мало, помимо стейта надо сгенерировать сам код, а руками перекладывать всё из выхлопа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt; весьма муторно.&lt;/p&gt;

&lt;h2 id=&quot;terraformer&quot;&gt;&lt;a href=&quot;https://github.com/GoogleCloudPlatform/terraformer&quot;&gt;terraformer&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Утилита, которая умеет ходить в API AWS, Cloudflare и ряда других сервисов, собирать оттуда информацию об объектах инфраструктуры и генерировать terraform-код и terraform-стейт.
По описанию звучит так, будто бы это серебрянная пуля, но на самом деле совсем нет.&lt;/p&gt;

&lt;p&gt;Плюсы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Код действительно генерируется и он даже работает, в большинстве случаев если прогнать по этому коду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform apply&lt;/code&gt; - всё будет ок;&lt;/li&gt;
  &lt;li&gt;Удобно, когда надо оценить фронт работ и хотя бы просто понять, а насколько много инфраструктуры создано в том же AWS;&lt;/li&gt;
  &lt;li&gt;Удобно, когда надо сделать “хоть как-то”, например если надо быстро отредачить кучу объектов, то может быть удобно “сдампить” их в terraform-код, отредактировать регуляркой и потом применить измененный код.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Минусы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Код низкого качества, куча дублированного кода, плохо с взаимосвязями ресурсов. Про модули я вообще молчу;&lt;/li&gt;
  &lt;li&gt;Не всегда поддерживаются актуальные версии ресурсов, например в aws-провайдере для S3 есть тенденция к дроблению большого ресурса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3_bucket&lt;/code&gt; на кучу мелких про policy, про lifecycle и так далее. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraformer&lt;/code&gt; дампит всё по старинке в один большой ресурс.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Я обычно использовал его вместе с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform import&lt;/code&gt; для кросс-верификации, чтобы убедиться что сгенерированный код совпадает с импортированным стейтом.&lt;/p&gt;

&lt;p&gt;Небольшой гайд по использованию если что есть тут: &lt;a href=&quot;https://blog.strangeman.info/devops/2023/01/30/terraformer-guide.html&quot;&gt;https://blog.strangeman.info/devops/2023/01/30/terraformer-guide.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Также есть утилита &lt;a href=&quot;https://github.com/magodo/tfadd&quot;&gt;tfadd&lt;/a&gt;, которая умеет генерировать tf-код из стейта (то есть сначала импортируем стейт через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform import&lt;/code&gt;, а потом генерируем из него код через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfadd&lt;/code&gt;), но я ей не пользовался.&lt;/p&gt;

&lt;h2 id=&quot;k2tf-и-tfk8s&quot;&gt;&lt;a href=&quot;https://github.com/sl1pm4t/k2tf&quot;&gt;k2tf&lt;/a&gt; и &lt;a href=&quot;https://github.com/jrhouston/tfk8s&quot;&gt;tfk8s&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Обе утилиты делают одно и то же - конвертируют YAML-манифесты kubernetes в terraform-код. Стейт предлагается импортировать через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform import&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfk8s&lt;/code&gt; мне показалась менее удобной, так как генерирует только &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes_manifest&lt;/code&gt; ресурсы, что выглядит некрасиво. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k2tf&lt;/code&gt; поумнее, она генерирует ресурсы типа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes_deployment&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes_service&lt;/code&gt; и так далее, что позволяет получить более качественный код.&lt;/p&gt;

&lt;p&gt;Но у &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k2tf&lt;/code&gt; есть минус - последний коммит был в середине прошлого года, есть ощущение что её подзабросили и в какой-то момент она может перестать поддерживать актуальные версии провайдеров.&lt;/p&gt;

&lt;h2 id=&quot;tfautomv&quot;&gt;&lt;a href=&quot;https://github.com/padok-team/tfautomv&quot;&gt;tfautomv&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Утилита не столько для переноса уже созданной инфры под terraform, сколько для рефакторинга уже имеющегося кода, но сюда она тоже подойдет, так как рефачить всё то что мы надобавляли неизбежно придется.&lt;/p&gt;

&lt;p&gt;Суть: вам потребовалось переименовать ресурс, или перенести его куда-то, или ещё что-то. Terraform естественно предложит вам снести старый ресурс и создать с нуля новый. Это может быть очень больно. Для облегчения страданий есть команда &lt;a href=&quot;https://developer.hashicorp.com/terraform/cli/commands/state/mv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform state mv&lt;/code&gt;&lt;/a&gt; и специальные &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/modules/develop/refactoring#moved-block-syntax&quot;&gt;moved-блоки&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Но даже c &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moved&lt;/code&gt; это всё ещё больно. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfautomv&lt;/code&gt; эту боль убирает, генерируя эти блоки автоматически. Грубо говоря, он смотрит выхлоп &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt; и, если видит идентичные ресурсы с разными именами на удаление и добавление, то кладет их в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moved-блок&lt;/code&gt;. Утилита работает не идеально, особенно если рефакторинг включает в себя не только переименование, но всё равно сильно облегчает жизнь.&lt;/p&gt;
</description>
        <pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/2023/01/30/terraform-import-tools.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/2023/01/30/terraform-import-tools.html</guid>
        
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>Как я новую работу искал</title>
        <description>&lt;p&gt;Перекладываю тредик из твиттера сюда, чтобы не потерялось в случае каких-то проблем.&lt;/p&gt;

&lt;p&gt;Первоисточник тут: &lt;a href=&quot;https://mobile.twitter.com/_strangeman/status/1551817451020615681&quot;&gt;https://mobile.twitter.com/_strangeman/status/1551817451020615681&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Небольшой тредик про то, как я собеседовался на новую работу, солью информацию о том, сколько денег нынче девопсам предлагают и что вообще на рынке есть. Может кому-то пригодится, да и просто свой опыт зафиксирую.&lt;/p&gt;

&lt;p&gt;Исходные данные, чего искал: позиция Senior/Lead DevOps/Platform/Infrastructure Engineer, обязательно с релокацией в Европу, обязательно в продуктовую компанию. На поиски заложил три месяца, уложился в итоге в четыре недели.&lt;/p&gt;

&lt;p&gt;Общался с восемью компаниями, получил пять офферов, одна компания отвалилась на пре-оффере (по локациям не сошлись) и две не ответили на резюме, видимо даже скрининг не прошел. Так что дальше будет инфа по тем шести компаниям, где были непосредственно интервью.&lt;/p&gt;

&lt;p&gt;Источники вакансий: две от рекрутеров, которым написал сам (перебрал чаты за год, выбрал адекватных и разослал им CV ), две через рефереров из Вастрик.Клуба (о вакансиях узнал из &lt;a href=&quot;https://engineer-petr.github.io/&quot;&gt;https://engineer-petr.github.io/&lt;/a&gt;), две нашли меня сами через Linkedin и тред “Ищу работу” в Вастрик.Клубе.&lt;/p&gt;

&lt;p&gt;Четыре компании русскоязычные/гибридные (часть команд говорят на русском, часть на английском). Полностью англоязычных было только две компании, и то, с моим B2 без разговорной практики я прошел все этапы. Так что без языка уехать вполне реально, подтягивать можно будет в процессе.&lt;/p&gt;

&lt;p&gt;Самое популярное место для релокации - Кипр (4 из 6), потом Грузия, Болгария, Сербия, Нидерланды. На первых этапах три компании предлагали Польшу, но потом дали заднюю, говорят что поляки перестали выдавать визы русским.&lt;/p&gt;

&lt;p&gt;Почти все компании предлагают два варианта - устроиться в штат в локации где у них есть офис, либо работать контрактором откуда угодно. Все компании явно оговаривали что из РФ работать не получится.&lt;/p&gt;

&lt;p&gt;Релокационные пакеты разные, от “билеты + две недели аренды” до “билеты + месяц аренды + 10к евро подъемных” или “билеты + месяц аренды + субсидия на аренду на первый год”. Пакеты включают семью, помогают с документами, визами, вот этим всем.&lt;/p&gt;

&lt;p&gt;Денег тоже хотят платить сильно по-разному, от 4.2к net до 6.7k net евро. Две компании дополнительно давали опционы (~100-200к в зависимости от оценки). Какой-то сильной взаимосвязи предложенной суммы и размера компании или суммы и должности не увидел.&lt;/p&gt;

&lt;p&gt;По процессу собесов: типовой флоу - скрининг, созвон с HR, техсобес, культурно-процессный собес с CTO/VP of Engineering, преоффер, оффер. В одной компании культурный собес был до техсобеса. В другой - техсобес вел Head of Infra, так что сразу про всё поговорили.&lt;/p&gt;

&lt;p&gt;Самый быстрый пайплайн от первого созвона с HR до оффера - четыре дня (три созвона). Самый длинный - 25 дней (четыре созвона). Сроки офферов - 1-2 недели обычно, в паре случаев срок не оговаривали.&lt;/p&gt;

&lt;p&gt;Предлагаемые тайтлы: DevOps Engineer (Principal, Senior), Infrastructure Engineer, Head of DevOps, Head of Developer Experience (красиво звучит, блин).&lt;/p&gt;

&lt;p&gt;Сами интервью сильно разные, кто-то по харду по технологиям спрашивает, кто-то наоборот в основном про процессы и подходы. Последнее мне конечно более близко и такие ребята были в приоритете. Много спрашивают про облака, много про кубер.&lt;/p&gt;

&lt;p&gt;Основные причины отказов от офферов: мало денег (ставил себе планку в 5k net), проблемы с процессами и коммуникацией в компании, ощущение, что придется крутить одну и ту же гайку 8 часов в день. В итоге выбрал небольшую компанию с похожим взглядом на процессы со стороны менеджмента.&lt;/p&gt;

&lt;h2 id=&quot;источник&quot;&gt;Источник&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://mobile.twitter.com/_strangeman/status/1551817451020615681&quot;&gt;https://mobile.twitter.com/_strangeman/status/1551817451020615681&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 26 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/recruting/2022/07/26/devops-job-searching.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/recruting/2022/07/26/devops-job-searching.html</guid>
        
        
        <category>devops</category>
        
        <category>recruting</category>
        
      </item>
    
      <item>
        <title>Как превратить кастомный запрос ClickHouse в метрики для Prometheus</title>
        <description>&lt;p&gt;Потребовалось тут положить в Prometheus информацию о наличии таблиц на репликах, плюс о движке этих таблиц, чтобы иметь возможность отлавливать ситуации, когда создается таблица без репликации, либо она создается не на всех репликах.&lt;/p&gt;

&lt;p&gt;Стандартными метриками эта информация не собирается. Но для решения этой проблемы в ClickHouse есть механизм
&lt;a href=&quot;https://clickhouse.com/docs/en/interfaces/http/#predefined_http_interface&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;predefined_query_handler&lt;/code&gt;&lt;/a&gt;, который позволяет отдавать по заданному URL результаты запроса в необходимом формате.&lt;/p&gt;

&lt;p&gt;Как это выглядит в конфигах. В первую очередь, нам надо определиться с запросом. Для нашего кейса он будет выглядеть просто:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT database,name,engine FROM system.tables&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Теперь этот запрос надо обернуть в корректное форматирование. Для этого используются параметры запроса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FORMAT Template&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SETTINGS format_template_*&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_template_row&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_template_resultset&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_template_rows_between_delimiter&lt;/code&gt;). Эти параметры описаны в &lt;a href=&quot;https://clickhouse.com/docs/ru/interfaces/formats/#format-template&quot;&gt;документации&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Нам надо задать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_template_row&lt;/code&gt; для форматирования каждой строки результата запроса под требования Prometheus. Для этого создаем файл &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/clickhouse/format_schemas/prometheus_table_engine.format&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# TYPE clickhouse_db_engines gauge
# HELP show table engines
clickhouse_db_engines{db_name=&quot;${database:Raw}&quot;, table=&quot;${name:Raw}&quot;,engine=&quot;${engine:Raw}&quot;} 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Также нам надо переопределить дефолтное значение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_template_rows_between_delimiter&lt;/code&gt; на пустую строку (по дефолту стоит &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; и это будет приводить к пустым строкам в списке метрик, что для Prometheus не подойдет).&lt;/p&gt;

&lt;p&gt;Теперь нам надо прикрутить этот запрос к какому-нибудь URL в ClickHouse. Это делается через конфиг, через секцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;http_handlers&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &amp;lt;http_handlers&amp;gt;
        &amp;lt;rule&amp;gt;
            &amp;lt;url&amp;gt;/table_engines&amp;lt;/url&amp;gt;
            &amp;lt;methods&amp;gt;GET&amp;lt;/methods&amp;gt;
            &amp;lt;handler&amp;gt;
                &amp;lt;type&amp;gt;predefined_query_handler&amp;lt;/type&amp;gt;
                &amp;lt;query&amp;gt;SELECT database,name,engine FROM system.tables FORMAT Template SETTINGS format_template_row = &apos;prometheus_table_engine.format&apos;, format_template_rows_between_delimiter = &apos;&apos; &amp;lt;/query&amp;gt;
            &amp;lt;/handler&amp;gt;
        &amp;lt;/rule&amp;gt;
        &amp;lt;defaults/&amp;gt;
    &amp;lt;/http_handlers&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Важно: внутри секции обязательно должно быть поле &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;defaults/&amp;gt;&lt;/code&gt;, иначе отломаются все стандартные эндпойнты.&lt;/p&gt;

&lt;p&gt;Итого, при запросе на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/table_engines&lt;/code&gt; нам вывалится набор метрик по таблицам и их движкам:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl http://localhost:8123/table_engines

# TYPE clickhouse_db_engines gauge
# HELP show table engines
clickhouse_db_engines{db_name=&quot;system&quot;, table=&quot;replication_queue&quot;,engine=&quot;SystemReplicationQueue&quot;} 1
# TYPE clickhouse_db_engines gauge
# HELP show table engines
clickhouse_db_engines{db_name=&quot;system&quot;, table=&quot;role_grants&quot;,engine=&quot;SystemRoleGrants&quot;} 1
# TYPE clickhouse_db_engines gauge
# HELP show table engines
clickhouse_db_engines{db_name=&quot;system&quot;, table=&quot;roles&quot;,engine=&quot;SystemRoles&quot;} 1
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;источник&quot;&gt;Источник&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://clickhouse.com/docs/en/interfaces/http/#predefined_http_interface&quot;&gt;https://clickhouse.com/docs/en/interfaces/http/#predefined_http_interface&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://clickhouse.com/docs/en/interfaces/formats/#format-template&quot;&gt;https://clickhouse.com/docs/en/interfaces/formats/#format-template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 17 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/monitoring/clickhouse/2022/02/17/clickhouse-query-to-prometheus.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/monitoring/clickhouse/2022/02/17/clickhouse-query-to-prometheus.html</guid>
        
        
        <category>devops</category>
        
        <category>monitoring</category>
        
        <category>clickhouse</category>
        
      </item>
    
      <item>
        <title>Edit all sentry projects with curl, jq and duct tape</title>
        <description>&lt;h2 id=&quot;script&quot;&gt;Script&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# get token from Sentry&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XXXXXXXXXXXXXXXXXXXXXXXXXX&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;SENTRY_ROOT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sentry.example.com
&lt;span class=&quot;nv&quot;&gt;SENTRY_ORG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;myorg

&lt;span class=&quot;c&quot;&gt;# easiest way to get payload - look into browser development tools and copy endpoints and jsons&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DATA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{&quot;allowedDomains&quot;:[&quot;*.example.work&quot;, &quot;*.example.net&quot;, &quot;*.example.io&quot;]}&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# get all projects&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;APPS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sb&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Accept: application/json&quot;&lt;/span&gt;  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TOKEN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; https://&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SENTRY_ROOT&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/api/0/organizations/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SENTRY_ORG&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/projects/ | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.[].slug&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# put settings to all projects&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;APP &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;APPS&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;curl &lt;span class=&quot;s2&quot;&gt;&quot;https://&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SENTRY_ROOT&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/0/projects/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SENTRY_ORG&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Pragma: no-cache&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Cache-Control: no-cache&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TOKEN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATA&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Mon, 23 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/bash/2019/12/23/senry-bulk-edit.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/bash/2019/12/23/senry-bulk-edit.html</guid>
        
        
        <category>bash</category>
        
      </item>
    
      <item>
        <title>Итоги 2018 года</title>
        <description>&lt;h1 id=&quot;минусы&quot;&gt;Минусы&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Низкая физическая активность. Шевелился сильно меньше, чем в прошлые годы. Меньше велосипеда, меньше игр, меньше прогулок. Как результат - довольно заметно расплылся и начало барахлить то тут, то там в теле;&lt;/li&gt;
  &lt;li&gt;Здоровье. Частично связано с прошлым пунктом, частично с чем-то еще, надо разбираться. Количество болячек заметно увеличилось, к сожалению.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;плюсы&quot;&gt;Плюсы&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Психотерапия. В июне закончил регулярные встречи с терапевтом, но стреляет до сих пор, постоянно вылезают те истории, которые “в теории” проговаривали на консультациях, и это очень круто. Это самое ценное, что произошло за год. Стало сильно больше порядка внутри - становится сильно проще наводить порядок снаружи меня. Из побочек - это наведение порядка снаружи, конечно, влияет на окружающих. Разошелся с девушкой. Разругался с сестрой, разругался с мамой, постепенно учимся общаться по-новому. Стало сильно больше свободы внутри, меньше стыда, зажимов и сожалений, как следствие - меньше внутреннего давления и потребности его снимать алкоголем, ебанутым поведением и прочим деструктивом. Стало меньше “я паршивый отец” в голове;&lt;/li&gt;
  &lt;li&gt;Люди. Сильно вырос (и в какой-то мере сменился) круг общения. Важные для меня старые связи, по счастью, почти не рвались (в основном терялись слабые связи), зато появилась масса новых, как слабых, так и сильных;&lt;/li&gt;
  &lt;li&gt;Путешествия. В лайт-режиме покатался по России, пару раз в Москву (там холодно и шумно, но красиво), посмотрел на Крым (там тепло и красиво);&lt;/li&gt;
  &lt;li&gt;Работа. Hard-skills, на мой взгляд, выросли слабо, качественных скачков как в 2016-2017 не было. Зато выросли soft-skills, повыступал с докладами как внутри компании, так и “снаружи”, на Uptime Day;&lt;/li&gt;
  &lt;li&gt;Финансы. Кажется, это первый год в моей жизни, когда я спокойно мог планировать бюджеты. Кончилась суета и запара. А может просто перестал заливать бесполезными покупками дырки внутри (см. выше про ебанутое поведение и бухлишко).&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;планы&quot;&gt;Планы&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Получить права, ибо к 30 годам возникла потребность;&lt;/li&gt;
  &lt;li&gt;Снова начать бегать и в целом больше заботиться о себе, ибо сидячая работа сама себе кровь не разгонит и здоровью не поспособствует;&lt;/li&gt;
  &lt;li&gt;Хочу в Грузию! И в Армению! И еще куда-нибудь! И шенген бы сделать. В общем ну вы поняли. План максимум - отправиться в долгоиграющее кочевье по нескольким местам, благо есть несколько успешных примеров перед глазами.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 07 Jan 2019 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/life/2019/01/07/year-summary.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/life/2019/01/07/year-summary.html</guid>
        
        
        <category>life</category>
        
      </item>
    
      <item>
        <title>Yet another &apos;How to speed up Ansible&apos; tips</title>
        <description>&lt;h2 id=&quot;ssh-settings&quot;&gt;SSH settings&lt;/h2&gt;

&lt;p&gt;ansible.cfg&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cfg&quot;&gt;[ssh_connection]
ssh_args = -o PreferredAuthentications=publickey -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PreferredAuthentications&lt;/code&gt; helps to initiate the connection faster (without trying rare &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gssapi-with-mic,gssapi-keyex,hostbased&lt;/code&gt; methods).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ControlMaster&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ControlPersist&lt;/code&gt; allows multiplexing existing connections to the server.&lt;/p&gt;

&lt;p&gt;Useful links: &lt;a href=&quot;https://serverfault.com/questions/929222/ansible-where-do-preferredauthentications-ssh-settings-come-from&quot;&gt;https://serverfault.com/questions/929222/ansible-where-do-preferredauthentications-ssh-settings-come-from&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;facts-caching&quot;&gt;Facts caching&lt;/h2&gt;

&lt;p&gt;ansible.cfg&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cfg&quot;&gt;[defaults]
gathering = smart
fact_caching=yaml
fact_caching_timeout=86400
fact_caching_connection=./ansible-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These options enable fact caching for 1 day and tell Ansible not gather facts in they already presented in the cache.&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/plugins/cache.html#enabling-fact-cache-plugins&quot;&gt;https://docs.ansible.com/ansible/latest/plugins/cache.html#enabling-fact-cache-plugins&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-gathering&quot;&gt;https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-gathering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;profiling&quot;&gt;Profiling&lt;/h2&gt;

&lt;p&gt;The most important part, in my opinion. There is no universal way, but I have some recomemndations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Try to make tasks idempotent as possible. No need to do a useless job;&lt;/li&gt;
  &lt;li&gt;Delete duplicating tasks. For example, you’re may try to enable the firewall every time when you’re installing something. These tasks may be idempotent, but they still consume execution time;&lt;/li&gt;
  &lt;li&gt;Use something like Ara to visualize the playbook execution. Ara provides a good web interface and allows to sort tasks for execution time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful links: &lt;a href=&quot;https://github.com/openstack/ara&quot;&gt;https://github.com/openstack/ara&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Jan 2019 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/ansible/2019/01/02/ansible-speedup.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/ansible/2019/01/02/ansible-speedup.html</guid>
        
        
        <category>ansible</category>
        
      </item>
    
      <item>
        <title>Анонимизация данных в PostgreSQL базе с помощью Python</title>
        <description>&lt;h1 id=&quot;table-of-contents&quot;&gt;Table Of Contents&lt;/h1&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#table-of-contents&quot; id=&quot;markdown-toc-table-of-contents&quot;&gt;Table Of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#постановка-задачи&quot; id=&quot;markdown-toc-постановка-задачи&quot;&gt;Постановка задачи&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#популярные-библиотеки&quot; id=&quot;markdown-toc-популярные-библиотеки&quot;&gt;Популярные библиотеки&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#реализация&quot; id=&quot;markdown-toc-реализация&quot;&gt;Реализация&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;постановка-задачи&quot;&gt;Постановка задачи&lt;/h1&gt;

&lt;p&gt;Есть дампы баз микросервисов с прода. Перед заливкой их на тест (чтобы гонять тестовую среду на приближенных к боевым объемах данных) хочется заменить там логины, имена, фамилии и адреса почты. Рандомно генерировать не подойдет, тестировщикам будет неудобно работать с Аваолртпщр Пватващитвап. Соответственно, надо взять какое-то решение для генерации фейковых данных.&lt;/p&gt;

&lt;p&gt;Помимо этого, после замены базы должны остаться консистентными, во всех базах один и тот же id должен соответствовать одному и тому же пользователю. Также должен быть whitelist пользователей, которых не надо обновлять. Ну и плюс при добавлении баз это все должно довольно легко расширяться.&lt;/p&gt;

&lt;p&gt;Писать буду на Python, ибо быстро и куча библиотек на все случаи жизни.&lt;/p&gt;

&lt;h1 id=&quot;популярные-библиотеки&quot;&gt;Популярные библиотеки&lt;/h1&gt;

&lt;p&gt;В ходе подготовки нашлись две популярные библиотеки для генерации фейков:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/joke2k/faker&quot;&gt;faker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/lk-geimfari/mimesis&quot;&gt;mimesis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;При тестировании выяснилось, что mimesis работает в несколько раз быстрее чем faker при одинаковом, в общем-то, функционале.&lt;/p&gt;

&lt;h1 id=&quot;реализация&quot;&gt;Реализация&lt;/h1&gt;

&lt;p&gt;Обе библиотеки обладают относительно ограниченным набором данных. В таблицах стояло ограничение на уникальность логина и емейла и в итоге через некоторое время записи начинали повторяться и все падало к хренам. Пришлось добавлять рандомный постфикс к логинам и емейлам. В итоге получился примерно такой класс для генерации фейкового аккаунта:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MimesisPerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascii_lowercase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Целиком скрипт выглядит примерно так:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;psycopg2&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;psycopg2.extras&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mimesis&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascii_lowercase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digits&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;config&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MimesisPerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascii_lowercase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;ru&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;psycopg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substitute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_login&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                                     &lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# update user info in base1 and base2
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base1_conn_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;Template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UPDATE users SET login = &apos;$user_login&apos;, email = &apos;$email&apos;, first_name = &apos;$first_name&apos;, last_name = &apos;$last_name&apos; WHERE id = &apos;$user_id&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;base2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn_string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base2_conn_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;Template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UPDATE users SET username = &apos;$user_login&apos;, mail = &apos;$email&apos;, first_name = &apos;$first_name&apos;, last_name = &apos;$last_name&apos; WHERE id = &apos;$user_id&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;base_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;id_cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor_factory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;psycopg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extras&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DictCursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;id_cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;SELECT id FROM users&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bulk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id_cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MimesisPerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id_whitelist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Skipping user_id %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error on %i entry in batch, exiting...&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bulk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;bulk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bulk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bulk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Processed %i entries...&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Новые базы при надобности просто добавляются в массив и по ним тоже начинает бегать скрипт.&lt;/p&gt;
</description>
        <pubDate>Fri, 19 Oct 2018 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/python/2018/10/19/python-anonimize.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/python/2018/10/19/python-anonimize.html</guid>
        
        
        <category>python</category>
        
      </item>
    
      <item>
        <title>Benchmarking Python vs Go+Python vs Rust+Python for Vincenty formula </title>
        <description>&lt;h1 id=&quot;table-of-contents&quot;&gt;Table Of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#table-of-contents&quot; id=&quot;markdown-toc-table-of-contents&quot;&gt;Table Of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#benchmarking-python-vs-gopython-vs-rustpython&quot; id=&quot;markdown-toc-benchmarking-python-vs-gopython-vs-rustpython&quot;&gt;Benchmarking Python vs Go+Python vs Rust+Python&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#disclaimer&quot; id=&quot;markdown-toc-disclaimer&quot;&gt;Disclaimer&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#environment-and-tools&quot; id=&quot;markdown-toc-environment-and-tools&quot;&gt;Environment and tools&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#os&quot; id=&quot;markdown-toc-os&quot;&gt;OS&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#languages&quot; id=&quot;markdown-toc-languages&quot;&gt;Languages&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#benchmarking&quot; id=&quot;markdown-toc-benchmarking&quot;&gt;Benchmarking&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#how-to-run-it&quot; id=&quot;markdown-toc-how-to-run-it&quot;&gt;How to run it&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#results&quot; id=&quot;markdown-toc-results&quot;&gt;Results&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#size-of-binaries&quot; id=&quot;markdown-toc-size-of-binaries&quot;&gt;Size of binaries&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#some-additional-thoughts&quot; id=&quot;markdown-toc-some-additional-thoughts&quot;&gt;Some additional thoughts&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;benchmarking-python-vs-gopython-vs-rustpython&quot;&gt;Benchmarking Python vs Go+Python vs Rust+Python&lt;/h1&gt;

&lt;h2 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;/h2&gt;

&lt;p&gt;This is not too representative experiment, I just searching the fastest way to compute &lt;a href=&quot;https://en.wikipedia.org/wiki/Vincenty%27s_formulae&quot;&gt;Vincenty distance&lt;/a&gt; for my Telegram Python bot.&lt;/p&gt;

&lt;p&gt;Algorithms for Go and Rust are equal (I just rewrote Go code into Rust), ‘plain’ Python uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geopy&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;Rust code may be not ‘rust-way’, but this is my first experience with this language.&lt;/p&gt;

&lt;p&gt;All code located here: &lt;a href=&quot;https://github.com/strangeman/vincenty-benchmark&quot;&gt;https://github.com/strangeman/vincenty-benchmark&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;environment-and-tools&quot;&gt;Environment and tools&lt;/h2&gt;

&lt;h3 id=&quot;os&quot;&gt;OS&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cat /etc/os-release
PRETTY_NAME=&quot;Debian GNU/Linux buster/sid&quot;

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

&lt;h3 id=&quot;languages&quot;&gt;Languages&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ python -VV
Python 3.6.4 (default, Jan  5 2018, 02:13:53)
[GCC 7.2.0]

$ go version
go version go1.9.2 linux/amd64

$ rustc -Vv
rustc 1.23.0 (766bd11c8 2018-01-01)
binary: rustc
commit-hash: 766bd11c8a3c019ca53febdcd77b2215379dd67d
commit-date: 2018-01-01
host: x86_64-unknown-linux-gnu
release: 1.23.0
LLVM version: 4.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Rust code was compiled with and without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-O&lt;/code&gt; (allow optimizations) option.&lt;/p&gt;

&lt;p&gt;Unfortunately, that code wasn’t compiling with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gccgo&lt;/code&gt; compiler, so I didn’t test it.&lt;/p&gt;

&lt;h3 id=&quot;benchmarking&quot;&gt;Benchmarking&lt;/h3&gt;

&lt;p&gt;I used pytest-benchmark 3.1.1. Rust and Go code was compiled to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.so&lt;/code&gt; library and called from Python code. All sources located in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/src&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Benchmark parameters: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rounds=1000, warmup_rounds=5, iterations=100&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-to-run-it&quot;&gt;How to run it&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;You need to have installed Python 3, Go and Rust;&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make install&lt;/code&gt; to install needed Python and Go dependencies;&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make compile&lt;/code&gt; to compile Go and Rust code;&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make benchmark&lt;/code&gt; to run benchmarks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;------------------------------------------------------------------------------------------------- benchmark: 4 tests ------------------------------------------------------------------------------------------------
Name (time in us)                               Min                 Max                Mean             StdDev              Median                IQR            Outliers  OPS (Kops/s)            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_benchmark_distance_rust_optimized      16.6895 (1.0)       35.6014 (1.0)       19.6811 (1.0)       2.8708 (1.0)       19.1832 (1.0)       1.3009 (1.06)      113;103       50.8103 (1.0)        1000         100
test_benchmark_distance_rust_default        22.5512 (1.35)      46.2139 (1.30)      26.5663 (1.35)      3.6022 (1.25)      25.8693 (1.35)      1.2280 (1.0)       113;198       37.6417 (0.74)       1000         100
test_benchmark_distance_go                  34.1294 (2.04)      68.4217 (1.92)      39.2860 (2.00)      3.5562 (1.24)      39.0943 (2.04)      2.7511 (2.24)       181;42       25.4543 (0.50)       1000         100
test_benchmark_distance_python             163.4739 (9.79)     365.9721 (10.28)    186.3141 (9.47)     21.2402 (7.40)     185.2215 (9.66)     15.8129 (12.88)       59;43        5.3673 (0.11)       1000         100
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Several runs show similar results. Full report you can see &lt;a href=&quot;https://github.com/strangeman/vincenty-benchmark/benchmark_report.txt&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looks like Rust code 2x faster than Go and almost 10x faster than Python.&lt;/p&gt;

&lt;h3 id=&quot;size-of-binaries&quot;&gt;Size of binaries&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -l1h build/

1,4K godistance.h
2,0M godistance.so
2,8M rustdistance_default.so
2,8M rustdistance_optimized.so

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

&lt;h2 id=&quot;some-additional-thoughts&quot;&gt;Some additional thoughts&lt;/h2&gt;

&lt;p&gt;Rust looks interesting, but it a bit complex than Go (especially in OOP part). Also, Rust is younger and have less third-party modules and tools.&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Jan 2018 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/go/rust/python/2018/01/15/vincenty-benchmark.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/go/rust/python/2018/01/15/vincenty-benchmark.html</guid>
        
        
        <category>go</category>
        
        <category>rust</category>
        
        <category>python</category>
        
      </item>
    
      <item>
        <title>Kafka operation tips</title>
        <description>&lt;h1 id=&quot;table-of-contents&quot;&gt;Table Of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#table-of-contents&quot; id=&quot;markdown-toc-table-of-contents&quot;&gt;Table Of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-to-increase-topic-replication-factor-in-kafka&quot; id=&quot;markdown-toc-how-to-increase-topic-replication-factor-in-kafka&quot;&gt;How to increase topic Replication Factor in Kafka&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#how-to-look-current-topic-configuration&quot; id=&quot;markdown-toc-how-to-look-current-topic-configuration&quot;&gt;How to look current topic configuration&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#how-to-change-topic-replication-factor&quot; id=&quot;markdown-toc-how-to-change-topic-replication-factor&quot;&gt;How to change topic Replication Factor&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-to-increase-partition-count&quot; id=&quot;markdown-toc-how-to-increase-partition-count&quot;&gt;How to increase partition count&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-to-fix-leader--1-for-kafka-partition&quot; id=&quot;markdown-toc-how-to-fix-leader--1-for-kafka-partition&quot;&gt;How to fix ‘Leader: -1’ for Kafka partition&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#how-it-looks-like&quot; id=&quot;markdown-toc-how-it-looks-like&quot;&gt;How it looks like&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#how-to-fix-it&quot; id=&quot;markdown-toc-how-to-fix-it&quot;&gt;How to fix it&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;how-to-increase-topic-replication-factor-in-kafka&quot;&gt;How to increase topic Replication Factor in Kafka&lt;/h1&gt;

&lt;h2 id=&quot;how-to-look-current-topic-configuration&quot;&gt;How to look current topic configuration&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --topic f6b77500-a1f9-4c1f-8fe1-291ae28aff47  --describe --zookeeper localhost:2181
 
Topic:f6b77500-a1f9-4c1f-8fe1-291ae28aff47  PartitionCount:1    ReplicationFactor:1 Configs:retention.ms=2592000000,segment.ms=86400000
    Topic: f6b77500-a1f9-4c1f-8fe1-291ae28aff47 Partition: 0    Leader: 3   Replicas: 3 Isr: 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We interested in following parameters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReplicationFactor&lt;/code&gt; (count of replicas for each partition in this topic)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Isr&lt;/code&gt; (ids of replicas which have latest actual data, same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;broker.id&lt;/code&gt; in kafka config)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Leader&lt;/code&gt; (‘main’ node for this partition)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example 1: ReplicationFactor=3, all nodes are available&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --topic 9146aeea-1b19-48a0-9001-c77e35d74721  --describe --zookeeper localhost:2181
 
Topic:9146aeea-1b19-48a0-9001-c77e35d74721  PartitionCount:1    ReplicationFactor:3 Configs:retention.ms=2592000000,segment.ms=86400000
    Topic: 9146aeea-1b19-48a0-9001-c77e35d74721 Partition: 0    Leader: 1   Replicas: 1,2,3 Isr: 1,3,2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Example 2: ReplicationFactor=3, one node is unavailable&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --topic 9146aeea-1b19-48a0-9001-c77e35d74721  --describe --zookeeper localhost:2181
 
Topic:9146aeea-1b19-48a0-9001-c77e35d74721  PartitionCount:1    ReplicationFactor:3 Configs:retention.ms=2592000000,segment.ms=86400000
    Topic: 9146aeea-1b19-48a0-9001-c77e35d74721 Partition: 0    Leader: 1   Replicas: 1,2,3 Isr: 1,3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-to-change-topic-replication-factor&quot;&gt;How to change topic Replication Factor&lt;/h2&gt;

&lt;p&gt;1) Need to create JSON file with new topic configuration, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;partitions&quot;:                       
    [{&quot;topic&quot;: &quot;f6b77500-a1f9-4c1f-8fe1-291ae28aff47&quot;,                   
      &quot;partition&quot;: 0,                   
      &quot;replicas&quot;: [1,2,3] }],           
&quot;version&quot;:1                          
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and store it somewhere on Kafka server (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/reassign.json&lt;/code&gt; in my case)&lt;/p&gt;

&lt;p&gt;2) Then need to apply this file&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-reassign-partitions --zookeeper localhost:2181 --execute --reassignment-json-file /tmp/reassign.json
 
Current partition replica assignment
 
{&quot;version&quot;:1,&quot;partitions&quot;:[{&quot;topic&quot;:&quot;f6b77500-a1f9-4c1f-8fe1-291ae28aff47&quot;,&quot;partition&quot;:0,&quot;replicas&quot;:[3]}]}
 
Save this to use as the --reassignment-json-file option during rollback
Successfully started reassignment of partitions {&quot;version&quot;:1,&quot;partitions&quot;:[{&quot;topic&quot;:&quot;f6b77500-a1f9-4c1f-8fe1-291ae28aff47&quot;,&quot;partition&quot;:0,&quot;replicas&quot;:[1,2,3]}]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3) Check how reassignment is going&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-reassign-partitions --zookeeper localhost:2181 --verify --reassignment-json-file /tmp/reassign.json
 
Status of partition reassignment:
Reassignment of partition [f6b77500-a1f9-4c1f-8fe1-291ae28aff47,0] completed successfully
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;how-to-increase-partition-count&quot;&gt;How to increase partition count&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --zookeeper localhost:2181 --alter --partitions 5 --topic 9bd6a5bf-5fdb-4900-91af-c7b6d560968f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;how-to-fix-leader--1-for-kafka-partition&quot;&gt;How to fix ‘Leader: -1’ for Kafka partition&lt;/h1&gt;

&lt;h2 id=&quot;how-it-looks-like&quot;&gt;How it looks like&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --topic _schemas --describe --zookeeper localhost:2181
Topic:_schemas    PartitionCount:1    ReplicationFactor:3    Configs:cleanup.policy=compact
    Topic: _schemas    Partition: 0    Leader: -1    Replicas: 1,2,3    Isr:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That partition has no In-Sync Replicas (Isr) and cannot elect a Leader. Usually, that means we can’t read/write from/to this partition.&lt;/p&gt;

&lt;h2 id=&quot;how-to-fix-it&quot;&gt;How to fix it&lt;/h2&gt;

&lt;p&gt;TIP. We can get a list of all topics that have partitions without Leader with this one-liner:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --describe --zookeeper localhost:2181 | grep &quot;Leader: -1&quot; | cut -f 2 | uniq | cut -f 2 -d &quot; &quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Kafka can do Unclean Leader Election. By default, Kafka elects partition leader from in-sync replicas, to guarantee data consistency. But, when the partition has no in-sync replicas, we can switch to a special mode, which can elect a leader from the random available replica. This is the dangerous operation, because we may lose some data, but this is better than nothing.&lt;/p&gt;

&lt;p&gt;1) Enable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unclean.leader.election.enable=false&lt;/code&gt; for topic:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --config unclean.leader.election.enable=true --zookeeper localhost:2181 --topic _schemas --alter
Updated config for topic &quot;_schemas&quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2) Restart Kafka&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl restart kafka
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3) Check that leader was elected and in-sync replicas was appeared&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --topic _schemas --describe --zookeeper localhost:2181
Topic:_schemas    PartitionCount:1    ReplicationFactor:3    Configs:unclean.leader.election.enable=false,cleanup.policy=compact
    Topic: _schemas    Partition: 0    Leader: 1    Replicas: 1,2,3    Isr: 3,1,2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4) Disable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unclean.leader.election&lt;/code&gt; back&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kafka-topics --config unclean.leader.election.enable=false --zookeeper localhost:2181 --topic _schemas --alter
Updated config for topic &quot;_schemas&quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;5) And restart Kafka again&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl restart kafka
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 15 Aug 2017 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/kafka/2017/08/15/kafka-tips.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/kafka/2017/08/15/kafka-tips.html</guid>
        
        
        <category>kafka</category>
        
      </item>
    
      <item>
        <title>Ansible-Semaphore UI Walkthrough</title>
        <description>&lt;h1 id=&quot;table-of-contents&quot;&gt;Table Of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#table-of-contents&quot; id=&quot;markdown-toc-table-of-contents&quot;&gt;Table Of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#introduction&quot; id=&quot;markdown-toc-introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#ui-walkthrough&quot; id=&quot;markdown-toc-ui-walkthrough&quot;&gt;UI Walkthrough&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#login-page&quot; id=&quot;markdown-toc-login-page&quot;&gt;Login Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#main-page&quot; id=&quot;markdown-toc-main-page&quot;&gt;Main Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#global-user-list&quot; id=&quot;markdown-toc-global-user-list&quot;&gt;Global User List&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#global-user-edit-dialog&quot; id=&quot;markdown-toc-global-user-edit-dialog&quot;&gt;Global User Edit Dialog&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#main-project-page&quot; id=&quot;markdown-toc-main-project-page&quot;&gt;Main Project Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#project-settings-page&quot; id=&quot;markdown-toc-project-settings-page&quot;&gt;Project Settings Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#task-templates-page&quot; id=&quot;markdown-toc-task-templates-page&quot;&gt;Task Templates Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#task-template-edit-page&quot; id=&quot;markdown-toc-task-template-edit-page&quot;&gt;Task Template Edit Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#inventory-page&quot; id=&quot;markdown-toc-inventory-page&quot;&gt;Inventory Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#key-list-and-key-edit-pages&quot; id=&quot;markdown-toc-key-list-and-key-edit-pages&quot;&gt;Key List and Key Edit Pages&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#repository-list-page&quot; id=&quot;markdown-toc-repository-list-page&quot;&gt;Repository List Page&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#project-team-page-and-add-project-user-dialog&quot; id=&quot;markdown-toc-project-team-page-and-add-project-user-dialog&quot;&gt;Project Team Page and Add Project User Dialog&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#task-launch-process&quot; id=&quot;markdown-toc-task-launch-process&quot;&gt;Task Launch Process&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;/h1&gt;

&lt;p&gt;Ansible-Semaphore is Open Source alternative for Ansible Tower (Web UI and API for launching Ansible Tasks). Source code &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore&quot;&gt;hosted&lt;/a&gt; on Github under MIT license. Semaphore supports LDAP authentification, bearer tokens for REST API, Telegram and Email alerts for the failed tasks and simple user roles system (under development, see &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/pull/413&quot;&gt;#413&lt;/a&gt; and &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/pull/405&quot;&gt;#405&lt;/a&gt; PRs).
It’s written on Go (backend) and Angular (frontend).&lt;/p&gt;

&lt;h1 id=&quot;ui-walkthrough&quot;&gt;UI Walkthrough&lt;/h1&gt;

&lt;p&gt;Current screenshots taken from Semaphore 2.4.1 + &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/pull/413&quot;&gt;#413&lt;/a&gt; and &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/pull/405&quot;&gt;#405&lt;/a&gt; PRs.&lt;/p&gt;

&lt;h2 id=&quot;login-page&quot;&gt;Login Page&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-login.png&quot; alt=&quot;Semaphore Login Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;main-page&quot;&gt;Main Page&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-main-page.png&quot; alt=&quot;Semaphore Main Page&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;List of all appeared events: Task Template/User/Project/Repository/etc Creation/Updating/Deletion, Task status updates, and much more;&lt;/li&gt;
  &lt;li&gt;Project List and Project Add button.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;global-user-list&quot;&gt;Global User List&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-global-user-list.png&quot; alt=&quot;Semaphore Global User List&quot; /&gt;
This is the list of all users in Semaphore.&lt;/p&gt;

&lt;p&gt;Explanation of some properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Alert&lt;/em&gt;: user will receive alerts about failed tasks to his email;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Admin&lt;/em&gt;: user can create new users and edit their information;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;External&lt;/em&gt;: user authenticates through external backend (currently only LDAP is supported). Admin can’t edit his username and password.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;global-user-edit-dialog&quot;&gt;Global User Edit Dialog&lt;/h2&gt;
&lt;p&gt;Normal and External user edit dialog:
&lt;img src=&quot;/static/img/posts/semaphore-global-user-edit.png&quot; alt=&quot;Semaphore Global User Edit Dialog&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;main-project-page&quot;&gt;Main Project Page&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-main-project-page.png&quot; alt=&quot;Semaphore Main Project Page&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Project Settings button and Project Menu:
    &lt;ul&gt;
      &lt;li&gt;Dashboard: this page&lt;/li&gt;
      &lt;li&gt;Task Templates: all task templates, available for launch&lt;/li&gt;
      &lt;li&gt;Inventory: Semaphore currently supports Static Inventory (same as ordinary Ansible inventory file). Dynamic Inventories not yet supported (see &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/issues/47&quot;&gt;#47&lt;/a&gt;). Also, there is possible to use inventory from your playbooks repo (and pass it via Ansible &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i&lt;/code&gt; flag);&lt;/li&gt;
      &lt;li&gt;Environment: variables, related to this project;&lt;/li&gt;
      &lt;li&gt;Key Store: SSH keys for playbook repository fetching and ansible SSH connections. Various Task Templates may use various keys;&lt;/li&gt;
      &lt;li&gt;Playbook Repositories: list of git repos with playbooks for this project. Currently supported only SSH fetching. Local repos and HTTPS repos isn’t supported;&lt;/li&gt;
      &lt;li&gt;Team: list of users belong to this project. Currently, users have 2 properties: Admin user can add new users to the project, Launch-Only user cannot edit or create new items (Task Templates, Inventories, Environments etc) in the project. Admin User can be also a Launch-Only user (e.g. in the case to prevent accidental editing/deletion of items) and can edit this property for himself.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Project activity: list of all events related to this project (similar to Events on the main Semaphore page)&lt;/li&gt;
  &lt;li&gt;Task history: list of all launched tasks. It contains:
    &lt;ul&gt;
      &lt;li&gt;Task name;&lt;/li&gt;
      &lt;li&gt;Playbook name (green - task successful, red - task failed, blue - task running, gray - task waiting in the queue);&lt;/li&gt;
      &lt;li&gt;Task start time (in local user time);&lt;/li&gt;
      &lt;li&gt;Task duration (rounded to minutes);&lt;/li&gt;
      &lt;li&gt;Task initiator.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;project-settings-page&quot;&gt;Project Settings Page&lt;/h2&gt;
&lt;p&gt;Settings are pretty simple: project name, Telegram chat id for alerting and on/off switch for alerts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-project-settings-page.png&quot; alt=&quot;Semaphore Project Settings Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;task-templates-page&quot;&gt;Task Templates Page&lt;/h2&gt;

&lt;p&gt;Launch-Only User (bottom half of screen) cannot copy or create new task templates.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-task-templates-page.png&quot; alt=&quot;Semaphore Task Templates Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;task-template-edit-page&quot;&gt;Task Template Edit Page&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-task-template-edit-page.png&quot; alt=&quot;Semaphore Task Template Edit Page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You should firstly create Key Store, Playbook Repository, and Inventory items and then create a Task Template item. If you use inventory from your playbook repo, then you should create an empty inventory without any content (will be fixed in &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/issues/328&quot;&gt;#328&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;inventory-page&quot;&gt;Inventory Page&lt;/h2&gt;

&lt;p&gt;Red item is marked as removed (it should be deleted, but used by some task templates).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-inventory-page.png&quot; alt=&quot;Semaphore Inventory Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;key-list-and-key-edit-pages&quot;&gt;Key List and Key Edit Pages&lt;/h2&gt;

&lt;p&gt;AWS, Digital Ocean, and Google Cloud can be created, by cannot use because Dynamic Inventory support not yet realized.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-key-list-page.png&quot; alt=&quot;Semaphore Key List Page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-key-edit-page.png&quot; alt=&quot;Semaphore Key Edit Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;repository-list-page&quot;&gt;Repository List Page&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-repo-list-page.png&quot; alt=&quot;Semaphore Repository List Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;project-team-page-and-add-project-user-dialog&quot;&gt;Project Team Page and Add Project User Dialog&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-project-team-page.png&quot; alt=&quot;Semaphore Project Team Page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-add-project-user-page.png&quot; alt=&quot;Semaphore Add Project User Page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;task-launch-process&quot;&gt;Task Launch Process&lt;/h2&gt;

&lt;p&gt;After clicking on Run button, Semaphore shows Run/Dry Run Dialog:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-dry-run-page.png&quot; alt=&quot;Semaphore Run/Dry Run Dialog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then, Semaphore put the task in the queue. Currently, Semaphore can execute only one task from all project at the same time, will be fixed in &lt;a href=&quot;https://github.com/ansible-semaphore/semaphore/pull/366&quot;&gt;#366&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the task on top of the queue, it starts. Semaphore and Ansible logs will be shown to the user and stored in the database.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-running-task-page.png&quot; alt=&quot;Semaphore Running Task Page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The user can view any finished task log.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/img/posts/semaphore-finished-task-page.png&quot; alt=&quot;Semaphore Finished Task Page&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/ansible/2017/08/05/semaphore-ui-guide.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/ansible/2017/08/05/semaphore-ui-guide.html</guid>
        
        
        <category>ansible</category>
        
      </item>
    
      <item>
        <title>Впечатления от системы мониторинга Sysdig</title>
        <description>&lt;h1 id=&quot;общая-информация&quot;&gt;Общая информация&lt;/h1&gt;

&lt;p&gt;Ребята из &lt;a href=&quot;http://www.sysdig.org/&quot;&gt;Sysdig&lt;/a&gt; делают два продукта: опенсорсную &lt;a href=&quot;https://github.com/draios/sysdig&quot;&gt;утилиту&lt;/a&gt; с htop-like UI для получения подробной информации о том, что вообще происходит в системе и очень продвинутую облачную &lt;a href=&quot;https://sysdig.com/product/&quot;&gt;систему&lt;/a&gt; мониторинга, которая из коробки умеет собирать кучу метрик, детектить установленные приложения, работать с контейнерами (как с одиночными контейнерами, так и с compose/swarm/k8s/mesos оркестрацией). Обе этих системы, насколько я понимаю, работают на одном ядре, которое собирает информацию о системе на низком уровне (в процессе установки подсовывается, в том числе, свой модуль ядра) и либо выплевыает это все в ncurses-UI, либо шлет на сервер.&lt;/p&gt;

&lt;h1 id=&quot;опенсорсная-утилита&quot;&gt;Опенсорсная утилита&lt;/h1&gt;

&lt;p&gt;По сути, это швейцарский нож, включающий в себя strace, htop, lsof, tcpdump, iftop, docker client и, наверное, что-нибудь еще. Можно смотреть статистику потребления ресурсов (cpu, ram, сеть, диск) по процессам/контейнерам, что и куда шлет по сети каждый процесс/контейнер, ошибки и логи на уровне системы/приложения, трейсы, открытые файловые дескрипторы и так далее и тому подобное. Очень полезной фишкой является возможность записи всего происходящего в системе в специальный файл для последующего анализа. Вот тут есть &lt;a href=&quot;https://www.youtube.com/watch?v=UJ4wVrbP-Q8&quot;&gt;видео&lt;/a&gt; с примерным обзором возможностей, рекомендую посмотреть.&lt;/p&gt;

&lt;p&gt;Из минусов - эта вундервафля (при запущенном curses-клиенте) жрет немало CPU, особенно на серверах с большим количеством приложений (и ими вызываемых событий). Но тут, наверное, ничего не поделаешь.&lt;/p&gt;

&lt;h1 id=&quot;облачный-мониторинг&quot;&gt;Облачный мониторинг&lt;/h1&gt;

&lt;p&gt;Мониторинг умеет все тоже самое, что консольная утилита, по части сбора общесистемной информации. Помимо этого, клиент мониторинга детектит установленные на сервере приложения (в том числе и в контейнерах) и собирает по ним метрики, например статистику запросов (и время их выполнения) MySQL/Postgres, время отклика Nginx/HAProxy, состояние JVM, ну и так далее. Для значительного числа интеграций не требуется никакого ручного вмешательства, все начинает собираться автоматически. Есть куча predefined дашбордов и алертов, которые можно подкрутить под себя, при желании.&lt;/p&gt;

&lt;p&gt;Также, клиент умеет собирать информацию об архитектуре микросервисов, запущенных поверх оркестраторов контейнеров (и красиво ее визуализировать), интегрироваться с AWS ECS, алертить в Slack/PagerDuty/VictorOps/OpsGenie.&lt;/p&gt;

&lt;p&gt;Есть полноценный (с ограничением в 15 хостов) триал на две недели. Вполне достаточно, чтобы покрутить его со всех сторон и принять решение, насколько оно необходимо.&lt;/p&gt;

&lt;p&gt;Есть, увы, и минусы. Основных претензий три:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Тяжелый клиент. В системных требованиях указано, что он может отъедать до 512MB RAM, 5-7% CPU, плюс генерирует весьма хороший сетевой трафик. Не знаю, насколько сильно это будет аффектить высоконагруженные сервера, но, думаю, это может быть заметно.&lt;/li&gt;
  &lt;li&gt;Веб-интерфейс - панель космолета. На странице информации о хосте показываются абсолютно все возможные интеграции и сервисы, даже те, которых и в помине нет на этом сервере. В итоге, на поиск нужной информации уходит довольно много времени. Хотелось бы переключатель, которые будет скрывать интеграции, по которым не приходит никаких данных.&lt;/li&gt;
  &lt;li&gt;Цена. 20-30$ за хост в месяц, на мой взгляд, ограничивают использование такого мониторинга большими серверами. Мониторить какой-нибудь K8S/DCOS-кластер из 5-7 здоровых машин такой штукой очень круто, а вот для россыпи мелких серверов выйдет очень накладно.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;выводы&quot;&gt;Выводы&lt;/h1&gt;

&lt;p&gt;Опенсорсную часть я водружу на все сервера, думаю. Очень удобная утилита для траблшутинга. А вот мониторинг пока стоит отложить до миграции в какой-нибудь K8S/DCOS, в текущем виде получается дороговато. Метрики приложений/системы можно пособирать и Prometheus’ом, а сложная контейнеризация у нас пока не используется. Но когда начнется плотная работа с контейнерами - sysdig окажется очень полезен.&lt;/p&gt;
</description>
        <pubDate>Mon, 10 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/monitoring/2017/04/10/sysdig-review.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/monitoring/2017/04/10/sysdig-review.html</guid>
        
        
        <category>monitoring</category>
        
      </item>
    
      <item>
        <title>Заметка о поездке в Москву</title>
        <description>&lt;h1 id=&quot;цели&quot;&gt;Цели&lt;/h1&gt;

&lt;p&gt;Вернулся с двухнедельной поездки в Москву. Путешествием я преследовал несколько целей:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Посмотреть на город и прикинуть, насколько он подходит мне для проживания;&lt;/li&gt;
  &lt;li&gt;Сходить на HighLoad++ 2016;&lt;/li&gt;
  &lt;li&gt;Познакомиться с коллективом, в котором работаю;&lt;/li&gt;
  &lt;li&gt;Познакомиться с ребятами из Express42 (Никита, спасибо за вискарь! Открою, когда в &lt;a href=&quot;https://telegram.me/devops_deflope&quot;&gt;DevOps Deflope News&lt;/a&gt; будет 1000 человек);&lt;/li&gt;
  &lt;li&gt;Сменить обстановку и поработать какое-то время из офиса.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Цели я более-менее выполнил (жаль только, не всех увидел кого хотел), надо бы их осмыслить.&lt;/p&gt;

&lt;h1 id=&quot;москва-как-город-для-проживания&quot;&gt;Москва как город для проживания&lt;/h1&gt;

&lt;h2 id=&quot;плюсы&quot;&gt;Плюсы&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Все есть. Продукты, услуги, люди, развлечения, качество жизни и городской среды. Город, в котором я живу, имеет огромные проблемы со всем вышеперечисленным.&lt;/li&gt;
  &lt;li&gt;Больше возможностей для развития и обучения. Регулярные конференции, митапы, да и просто посиделки за пивом с беседами на технические темы.&lt;/li&gt;
  &lt;li&gt;Больше возможностей для карьерного роста. Моя удаленность от центра России и Европы (равно как и США) накладывает большие ограничения на выбор места работы. С другой стороны, в данный момент передо мной такой проблемы не стоит, я доволен своим текущим местом, интересных задач тут хватит еще надолго.&lt;/li&gt;
  &lt;li&gt;Больше возможностей для путешествий. У нас тут из адекватных (по цене и времени в пути) направлений только Китай и Корея, ну и Юго-Восточная Азия отчасти (Таиланд, Вьетнам). С Москвы доступна вся Европа, часть Африки и Азии, плюс до Таиланда лететь всего на час дольше.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;минусы&quot;&gt;Минусы&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Огромные расстояния. Мой офис находился в 40 минутах езды на метро. По московским меркам близко, но эта каждодневное мотание туда-сюда меня адски вымотало. Много времени, много шума, много людей вокруг, я очень плохо все это воспринимаю. Моя девушка (мы вместе ездили) добиралась до своего места учебы по полтора часа в один конец, это еще хуже. Аналогично, если хочется куда-то выбраться - будь готов убить один или несколько часов времени на добирание. Ну или сиди в своем районе и довольствуйся тем, что здесь есть (а в пределах района часто есть абсолютно все что нужно).&lt;/li&gt;
  &lt;li&gt;Много людей. Не только в метро, но и на улицах, в различных заведениях и т.д. Аналогично, очень выматывает. Кому-то наверное наоборот в кайф быть в густонаселенных городах, но я не из их числа.&lt;/li&gt;
  &lt;li&gt;Быстрый ритм жизни. Собственно, расходы на перемещение вынуждают сокращать время на все остальное, начинаешь жить в мягком цейтноте.&lt;/li&gt;
  &lt;li&gt;Стоимость жизни. В целом, на продукты и базовые развлечения у нас уходило примерно столько же, сколько и в родном городе. Но жилье и услуги стоят намного дороже. Плюс постоянные траты на метро. Плюс богатство развлечений стимулирует тратить на них довольно много денег.&lt;/li&gt;
  &lt;li&gt;Погода. Отдельный ад. Я понимаю, что я приехал в неудачное время, но такая серость и слякоть - это кошмар. Целыми днями пасмурно (и я так понимаю - это стандартная картина для осени и весны), постоянный переменный то дождь, то снег, мрак. Да, значительно теплее, чем на ДВ, но и значительно слякотнее и сырее. Лучше уж мороз, чем каша под ногами. Выдержка из Википедии про Хабаровск (у нас сходный климат): “Количество солнечных дней в году существенно выше, чем во многих крупных городах России (до 300 дней в году; в Москве и Санкт-Петербурге — ок. 100)”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;выводы&quot;&gt;Выводы&lt;/h2&gt;
&lt;p&gt;За две недели я очень сильно вымотался и понял что жить в Москве не хочу. Это надо либо очень постараться и найти квартиру недалеко от офиса/центра (и иметь очень хороший доход, чтобы ее оплачивать), либо работать дома и дальше своего района носа не высовывать. Так что наиболее вменяемым вариантом мне кажется переезд куда-то в не сильно большой город европейской части России (либо ближнего зарубежья), чтобы иметь возможность легко добраться до столицы, но не сталкиваться с ней каждодневно.&lt;/p&gt;

&lt;h1 id=&quot;highload-2016&quot;&gt;HighLoad++ 2016&lt;/h1&gt;

&lt;p&gt;Очень крутое мероприятие, хотя с непривычки было тяжело - постоянная толпа людей, да и вникать в доклады два дня подряд с утра до вечера непросто. Развиртуализировался с ребятами с Express 42, познакомился с Костей Назаровым, послушал про всякие интересные и не очень штуки. Понял, что навыки нетворкинга у меня никакие, надо над этим работать. :)&lt;/p&gt;

&lt;p&gt;В целом, буду стараться ездить на подобные крупные мероприятия хотя бы раз в год. Полезно и с коллегами познакомиться, и встряхнуть свою голову, послушав как другие люди решают свои проблемы.&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Nov 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/life/2016/11/17/moscow-trip.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/life/2016/11/17/moscow-trip.html</guid>
        
        
        <category>life</category>
        
      </item>
    
      <item>
        <title>Как преобразовать один массив хэшей в другой</title>
        <description>&lt;h2 id=&quot;задача&quot;&gt;Задача&lt;/h2&gt;

&lt;p&gt;Есть в Ansible массив хэшей с данными о сервисах:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;apps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;app_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;app1&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;db_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;db1&quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;db_owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;own1&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;another_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;somevalue&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;app_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;app2&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;db_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;db2&quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;db_owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;own2&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;another_key2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;somevalue2&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Я хочу этот массив преобразовать в другой, для того чтобы передать плейбуку для создания баз:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;databases&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;db1&quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;own1&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;db2&quot;&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;own2&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;На голом Python это было бы легко, а вот с Jinja2 пришлось повозиться немного, в основном из-за постоянных истерик YAML на не нравящиеся ему скобочки да кавычки.&lt;/p&gt;

&lt;h2 id=&quot;итоговый-код&quot;&gt;Итоговый код&lt;/h2&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;pre_tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;set dict with postgresql bases&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;set_fact&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;postgres_bases={{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;postgres_bases|default([])&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;name&apos;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;item.db_name,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;owner&apos;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;item.db_owner&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;with_items&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;apps&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;P.S. Дурацкий Jekyll сходит с ума от двойных фигурных скобок. :-/&lt;/p&gt;
</description>
        <pubDate>Mon, 03 Oct 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/ansible/2016/10/03/ansible-list-of-dict-morph.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/ansible/2016/10/03/ansible-list-of-dict-morph.html</guid>
        
        
        <category>ansible</category>
        
      </item>
    
      <item>
        <title>Обратная совместимость модулей между Ansible 2.1 и Ansible 2.0</title>
        <description>&lt;h2 id=&quot;вводная-tldr&quot;&gt;Вводная (TL;DR)&lt;/h2&gt;

&lt;p&gt;Столкнулся тут с неожиданно неприятным поведением Ansible, надо бы задокументировать опыт. В 2.1 изменился алгоритм обработки передаваемых модулю аргументов без явного указания типа. Если в 2.0 они передавались “как есть”, передали вы ему list или dict, так он таким и остался, то в 2.1 такие аргументы начали конвертироваться в строку. Вот &lt;a href=&quot;https://github.com/ansible/ansible/commit/62765858825173dd532827fe4c1dfb246eb8db47#diff-90085fdcec6ed8b273ba885eaee60328&quot;&gt;коммит&lt;/a&gt;. Для передачи аргументов “как есть” ввели новый тип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raw&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;проблема&quot;&gt;Проблема&lt;/h2&gt;

&lt;p&gt;Ничего не предвещало беды, я выкатывал новую версию приложения (хорошо хоть на тестовое окружение), Ansible отработал без ошибок. Но приложение не поднялось. Полезли с разработчиками на сервер, визуально все лежит на своих местах, в логах малоинформативные ошибки, дескать не могу загрузить такие-то модули (хотя они лежат на своих местах).&lt;/p&gt;

&lt;p&gt;Наконец, через некоторое время, нашли проблему. Кусок json-конфига вместо&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;existing_key&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;first&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;second&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;выглядел вот так (кавычки, обрамляющие список):&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;existing_key&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[&apos;first&apos;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;second&apos;]&quot;&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Для работы с json у нас используется самописный &lt;a href=&quot;https://github.com/UnitedTraders/ansible-role-jsonpatch&quot;&gt;модуль&lt;/a&gt;. Стало очевидно что дело в нем. Опытным путем установил, что под 2.0 он работал корректно, а под 2.1 подложил свинью.&lt;/p&gt;

&lt;h2 id=&quot;решение&quot;&gt;Решение&lt;/h2&gt;

&lt;p&gt;Документация по написанию модулей для Ansible крайне бедна, поэтому почти сразу пришлось лезть в код класса &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnsibleModule&lt;/code&gt;, что в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible/lib/ansible/module_utils/basic.py&lt;/code&gt;. Выяснил изменения в преобразовании типов (что описано во вводной), добавил к нужному аргументу тип &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raw&lt;/code&gt;, под 2.1 все заработало корректно, зато сломалось в 2.0:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;TASK [Patch JSON - add key] ****************************************************
fatal: [localhost]: FAILED! =&amp;gt; {&quot;changed&quot;: false, &quot;failed&quot;: true, &quot;msg&quot;: &quot;implementation error: unknown type raw requested for value&quot;}
        to retry, use: --limit @test.retry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ломать совместимость не хотелось, поэтому в результате родился вот такой код:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnsibleModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;argument_spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;raw&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_CHECK_ARGUMENT_TYPES_DISPATCHER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnsibleModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;argument_spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;raw&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Проверяем, есть ли тип ﻿⁠⁠⁠⁠&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raw﻿⁠⁠⁠⁠&lt;/code&gt; среди поддерживаемых, если есть, то переопределяем экземпляр класса ﻿⁠⁠⁠⁠&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnsibleModule﻿⁠⁠⁠⁠&lt;/code&gt; с ним.&lt;/p&gt;

&lt;p&gt;Не знаю, насколько много людей затрагивает такая проблема, но в нашем случае это изменение в поведении оказалось критичным. Большинство, наверное, и так передают в модуль строки, в этом же случае нам надо передавать самые разные типы: строки, числа, массивы (мало ли что в json понадобится вставить) и преобразование в строку для всего ломает логику модуля.&lt;/p&gt;
</description>
        <pubDate>Sat, 03 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/ansible/2016/09/03/ansible-compability.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/ansible/2016/09/03/ansible-compability.html</guid>
        
        
        <category>ansible</category>
        
      </item>
    
      <item>
        <title>Краткое сравнение систем оркестрации контейнеров</title>
        <description>&lt;p&gt;Информация актуальна где-то на конец июля.&lt;/p&gt;

&lt;p&gt;Вынесу сюда краткое summary по исследованию существующих систем оркестрации в формате “плюсы и минусы”, сделанному мной на стажировке в Экспресс42. В статью не вошла связка Mesos+Marathon, т.к. ее ставил не я, но остальное вроде все охватил. Статейка больше для себя, чтоб иметь краткое описание систем в одном месте.&lt;/p&gt;

&lt;h2 id=&quot;fleet&quot;&gt;Fleet&lt;/h2&gt;

&lt;p&gt;Fleet очень круто сделан идеологически - ты работаешь с целым кластером как с обычной системой, описывая обычные, в общем-то, юнит-файлы для systemd. В части эксплуатации лишних сущностей минимум, все они скрыты под капотом, под который лезть приходится не очень часто. Но с другой стороны, эта скрытость вызывает проблемы с пониманием логики системы, когда тебе надо не просто бездумно шлепать по мануалу, а делать что-то свое. Также, fleet бедноват на средства работы со сложными системами, у него урезанные зависимости, нет иерархий, окружений.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть constraints (как на уровне “запусти на ноде с такими-то свойствами”, так и на уровне “запусти/не запускай сервис2 на ноде с сервис1”)&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Нет healthchecks ни в каком виде&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть нормальный DNS Service Discovery, хоть и за счет внешних сервисов&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Удобный способ описания всего кластера в одном файле cloud-init&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Зависимости есть, но в очень урезанном виде: они работают только в пределах одной ноды. То есть мы можем запустить Сервис2 после Сервиса1, но только если оба этих сервиса запущены на одной ноде. Более сложные зависимости будут “когда-нибудь”&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть изолированная оверлейная сеть, с которой легко вытащить контейнер наружу, при желании (через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--net=host&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Поддерживается все множество docker-команд в юнитах&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; По файлу на сервис. Намного удобнее, чем отслеживать изменения в огромном файле на несколько сотен строк.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Сложно делать blue-green потому что сложно развернуть две инфраструктуры в пределах одного кластера. Неудобно делать partial deploy&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Зато rolling довольно безопасен за счет большого количества стадий. Хотя средств отката я не увидел, придется какой-то внешней системой назад катить&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Простой синтаксис. Ребята не изобретали свой формат, а взяли обычный systemd синтаксис.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;nomad&quot;&gt;Nomad&lt;/h2&gt;

&lt;p&gt;В Nomad есть то, чего нет в Docker - описание инфраструктуры в едином файле, healthchecks в том же описании, dry-run, но зато не хватает всего остального: нет нормальной работы ни с сетью, ни с персистентными данными. Для эксплуатации в наших условиях как-то совсем не годится.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; &lt;strong&gt;Важно&lt;/strong&gt;: до сих пор нет поддержки Docker volumes! - &lt;a href=&quot;https://github.com/hashicorp/nomad/issues/150&quot;&gt;https://github.com/hashicorp/nomad/issues/150&lt;/a&gt; &lt;a href=&quot;https://github.com/hashicorp/nomad/issues/62&quot;&gt;https://github.com/hashicorp/nomad/issues/62&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; &lt;strong&gt;Важно&lt;/strong&gt;: Номад не рестартит контейнеры с проваленными healthcheck. Просто помечает их в консуле как failed, но не перезапускает - &lt;a href=&quot;https://github.com/hashicorp/nomad/issues/876&quot;&gt;https://github.com/hashicorp/nomad/issues/876&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Нельзя рестартануть таску через nomad cli. Надо идти на слейв и прибивать контейнер руками.&lt;/li&gt;
  &lt;li&gt;Файл получается довольно объемный из-за JSON-подобного синтаксиса. Скобки-скобки-скобки. Но вполне читаемый.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Номад не умеет в зависимости между сервисами, когда-нибудь обещают сделать - &lt;a href=&quot;https://github.com/hashicorp/nomad/issues/419&quot;&gt;https://github.com/hashicorp/nomad/issues/419&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Номад не умеет биндить порты на все интерфейсы или на какой-то определенный для контейнера. Только на тот, который указан в параметре &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network_interface&lt;/code&gt; конфига слейва. Обещают пофиксить когда-нибудь - &lt;a href=&quot;https://github.com/hashicorp/nomad/issues/646&quot;&gt;https://github.com/hashicorp/nomad/issues/646&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Зато healthcheck’и можно описывать прямо тут, что удобно. Поддерживаются http, tcp и script чеки.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Некрасивые DNS-имена. Мало того что надо извращаться с dnsmasq/bind для того чтобы пробросить DNS консула на 53 порт, так еще и имена получаются некрасивые, по типу &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datastore-zookeeper.service.consul&lt;/code&gt;, вместо более цивильных &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zookeeper.datastore.service.consul&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Можно передавать данные о private registry прямо в самом файле, удобно.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Нет способов исключить повторяемость кода. Надо тебе передавать одни и те же переменные в 10 разных контейнеров - дублируй код 10 раз.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Nomad умеет обновлять джобу через скармливание ему новой версии hcl-файла, при этом обновляя контейнеры с заданными интервалами и параллелизмом.&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Поддерживается dry-run режим запуска джобы.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;kubernetes&quot;&gt;Kubernetes&lt;/h2&gt;

&lt;p&gt;Kubernetes очень крутой, но документация, особенно для такой объемной и сложной системы, просто ужасна. Мануалы часто неактуальны, некоторые вещи вообще в ней отсутствуют и приходится рыться в огромных обсуждениях на Github. А так - в нем есть почти все, хоть и местами через жопу.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть constraints, healthchecks, DNS Service Discovery, scaling, autoscaling, single tasks, rolling updates, кофеварка и триммер для волос в носу;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть cli утилита, которая может работать через TLS;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть какие-то политики безопасности, но я их не трогал. Вроде как можно авторизовать пользователей по ключу и позволять им разное;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть богатые возможности по определению своих метаданных для сервисов/подов/и т.д. Очень удобно в больших инфраструктурах;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть API, документировано тоже не прям супер;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть красивенький Dashboard&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Нет cron tasks, но есть четкий milestone, когда их запилят - в следующей версии&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Удобный способ описания всего кластера в одном файле cloud-init&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Удобный способ дистрибуции всего кластерного добра в контейнере, все развертывание сводится к указанию этому контейнеру нужных ключей&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть изолированная оверлейная сеть, с которой можно вытащить контейнер наружу, при желании через load balancer&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; Есть поддержка распределенных/сетевых ФС из коробки;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-up&quot;&gt;&lt;/i&gt; По умолчанию все довольно сильно изолировано, как на уровне namespace’ов, так и вообще by design. С другой стороны - зато сложновато взаимодействие сервисов настраивать;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Очень много сущностей на всех уровнях. Kubectl имеет довольно длинные команды (хотя есть completions). Искренне не понимаю, зачем нужно было городить 4 сущности: Pod, Service, ReplicaSet, Deployment там, где другие системы обходятся одной;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Как следствие из предыдущего - очень многословные yaml-файлы. Описание 4 сервисов у меня заняло 300 строк там, где в Swarm можно было бы обойтись 4 командами;&lt;/li&gt;
  &lt;li&gt;&lt;i class=&quot;fa fa-thumbs-down&quot;&gt;&lt;/i&gt; Помимо количества сущностей, они еще и плохо документированы. Ладно бы еще документация местами отсутствовала и приходилось лезть на Github, так она просто неправильная и неактуальная. Ты видишь мануал на официальном сайте, который призван решить твою проблему, а он неточный и устаревший. В итоге ты убиваешь кучу времени на этот мануал, а потом все равно в итоге лезешь на Github.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;swarm&quot;&gt;Swarm&lt;/h2&gt;

&lt;p&gt;Мне оркестрация от Docker’а очень понравилась. Реально не хватает только описания инфраструктуры как кода где-нибудь в одном файле, с зависимостями и переменными. А так: кластер развертывается быстро, никаких непонятных дополнительных сущностей, есть приватные сети, есть общие сети, есть балансировка, скейлинг, constraints, вот это вот все. Учитывая темпы развития Docker - все скоро будет совсем хорошо.&lt;/p&gt;

&lt;h2 id=&quot;aurora&quot;&gt;Aurora&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Aurora не умеет запускать несколько джобов с зависимостями между ними (например, запусти zk, как только он запустится - запусти kafka и т.д.). Одновременно, впрочем, она тоже не умеет их запускать, только по одному за раз. &lt;a href=&quot;http://mail-archives.apache.org/mod_mbox/aurora-user/201602.mbox/%3CEE9100CC-04FF-4C64-B479-D3DF68D8C0BE%40lensa.com%3E&quot;&gt;http://mail-archives.apache.org/mod_mbox/aurora-user/201602.mbox/%3CEE9100CC-04FF-4C64-B479-D3DF68D8C0BE%40lensa.com%3E&lt;/a&gt; - тут и далее по треду предлагают городить свои скрипты или использовать что-то вроде Chronos или Airflow.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;constraints работают на уровне параметра attributes mesos-slave. Т.е. определяем на слейве какие-то атрибуты и потом можем сказать авроре - запускай такой-то джоб только на слейве с таким-то атрибутом.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Healthchecks есть, http и shell, но я их не тестил еще.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Максимальное количество failures, timeout между попытками перезапуска есть.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Внутри aurora-файла полноценный питон, можно определять какие-то структуры, подсовывать их в таски, стрелять себе в ногу из револьвера, ружья, дробовика, пулемета. Мне оказалось удобно определить dict со всеми параметрами в шапке файла и обращаться к нему по ходу дела.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Целиком игнорируются ENV, ENTRYPOINT и COMMAND докерфайла. С одной стороны это плюс, можно один и тот же контейнер переиспользовать для разных тасок, плюс в ряде случаев можно обойтись и без пересборки контейнеров.
С другой стороны - приходится все эти переменные и команды вытаскивать из докерфайла в аврорафайл, получается что-то вроде:&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;postgres_proc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;postgres_daemon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;cmdline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
       export PGDATA=&apos;/pgdata&apos;
       export PG_MAJOR=&apos;9.3&apos;
       export PG_VERSION=&apos;9.3.13-1.pgdg80+1&apos;
       export PATH=&quot;/usr/lib/postgresql/$PG_MAJOR/bin:$PATH&quot;
       /docker-entrypoint.sh postgres
   &quot;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Есть cronjobs, services (long-running jobs) и обычные “одноразовые” джобы. С учетом предыдущего пункта все три вида можно построить на базе одного контейнера, переопределяя &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmdline&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Через aurora-cli можно логиниться прямо в контейнер джобы и чего-нибудь херачить там руками.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 21 Aug 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/devops/docker/2016/08/21/docker-orchestration-summary.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/devops/docker/2016/08/21/docker-orchestration-summary.html</guid>
        
        
        <category>devops</category>
        
        <category>docker</category>
        
      </item>
    
      <item>
        <title>Заметка о стажировке в Экспресс42</title>
        <description>&lt;p&gt;Хочу немного собрать мысли в кучу и осмыслить то, что со мной произошло за последние месяцы.&lt;/p&gt;

&lt;p&gt;Вкратце: с обычного офисного админа в небольшом городе, занимающегося всем, от протяжки сети и ползания под столами пользователей до попыток изучения современных средств администрирования, типа Ansible и Docker, я дорос до более-менее уверенного devops-специалиста, работающего с московскими компаниями.&lt;/p&gt;

&lt;p&gt;Честно говоря, я думал что вхождение в индустрию будет намного более долгим и болезненным. Наверное, стоит сказать спасибо кризису, если б не он, то мое прошлое место работы (то самое, где я работал офисным админом) не начало бы загибаться, я так бы и изучал все потихоньку в свободное время и раскачался на бы смену работы только через пару лет. Но тут в конторе начались проблемы, началось урезание бюджетов, сокращения (на момент моего ухода оттуда из 7 айтишников осталось лишь двое, при не сильно уменьшившейся инфраструктуре) и требования сделать хорошо из говна и палок.&lt;/p&gt;

&lt;p&gt;Естественно, корявые решения проблем порождали новые проблемы, развития становилось все меньше, тушения пожаров все больше и где-то с января-февраля я начал интенсивно искать новую работу. Тут стоит отметить, что ИТ-сфера у нас в городе развита крайне плохо и я работал в одной из самых продвинутых в этом плане компаний, которая хоть и не занималась непосредственно ИТ-бизнесом (а была просто сетью магазинов мебели и стройматериалов), но хотя бы имела отлаженные процессы и понимание значимости ИТ в современном бизнесе. В итоге, в городе у меня, фактически, было всего два пути: идти к какому-нибудь провайдеру, либо идти на какой-нибудь завод. Ни тот, ни другой вариант меня не устраивал.&lt;/p&gt;

&lt;p&gt;Другой вариант - идти во фриланс - мне тоже не сильно нравился, и я держал его на самый крайний случай. У меня был неплохой приработок на Upwork, при занятости 10-12 часов в неделю я получал почти столько же, сколько в офисе, но основным источником дохода делать я его не хотел. Причин этому три. Первая: отсутствие роста, я бы засел в своей нише LAMP/LEMP-админа и мне было бы тяжело с нее выбираться. На фоне нынешнего сокращения роли “олдскульных” админов это виделось мне тупиковым путем. Вторая причина: слишком длинная цепочка между заказчиком и исполнителем, любая проблема в этой цепочке могла оставить меня без денег или без наработанной репутации. Проблемы с аккаунтом на Upwork, проблемы с Paypal/Payoneer, проблемы с картой, какие-нибудь поправки в законодательстве, затрудняющие получение денег из-за рубежа - и все, швах. В итоге, кстати говоря, так и вышло, и я окончательно отказался от работы на Upwork: Payoneer три месяца не мог прислать мне карту, Upwork отобрал у меня Top Rated статус (послали мне письмо с требованием перепройти верификацию, которое я не получил и они просто молча забрали у меня статус), плюс все эти новости о падении капитализации Upwork и куча новых дополнительных комиссий окончательно разрушили мое доверие к этому сервису. Ну и третья проблема - необходимость постоянного поиска клиентов, ты, условно говоря, три часа работаешь, и еще три часа ищешь, договариваешься, продаешь себя. Мне это не нравится и очень утомляет.&lt;/p&gt;

&lt;p&gt;Уехать из города куда-нибудь в центральную часть России я пока не могу по личным причинам, так что у меня оставался только один вариант - full-time удаленная работа. Для зарубежного рынка я чувствовал себя не слишком уверенно, хотя ради пробы даже собеседовался в один французский стартап, так что где-то с начала марта стал искать вакансии по России. Целенаправленно искал вакансии для джуниоров, т.к. у меня не было понимания своего уровня относительно требований рынка и относительно других специалистов.&lt;/p&gt;

&lt;p&gt;К тому времени я уже довольно активно учился на Хекслете, так что отправил резюме в &lt;a href=&quot;http://hexlet-source.com/hr/&quot;&gt;Hexlet-source&lt;/a&gt; (но получил отказ), записался на публичное собеседование (но из-за проблем с поиском собеседующих &lt;a href=&quot;https://www.youtube.com/watch?v=C8SlKzeLAgQ&quot;&gt;прошел&lt;/a&gt; его только в начале мая). Где-то в середине марта в твиттере Кирилла Мокевнина я увидел вакансию на &lt;a href=&quot;http://express42.com/job.html&quot;&gt;должность&lt;/a&gt; devops-инженера в Express42 и решил попробовать по принципу “ну, за спрос денег не берут”. Вообще, про эту вакансию я слышал еще в феврале в подкасте &lt;a href=&quot;http://devopsdeflope.ru&quot;&gt;Devops Deflope&lt;/a&gt; (его как раз ведут ребята из Экспресса), но тогда не уделил ей особого внимания. Сейчас же, на мой взгляд, она подходила мне идеально - за трехмесячную стажировку меня обещали обучить devops-тулсету, да еще и платили за это неплохую, по меркам моего города, стипендию.&lt;/p&gt;

&lt;p&gt;Честно говоря, слабо верилось что меня возьмут (7 часов разницы с Москвой все таки, я совершенно не понимал как буду учиться при таком рассинхроне), но в итоге я прошел. Ребята решили протестировать удаленное обучение сразу в тяжелом режиме, с максимальной разницей во времени. :) С середины мая по середину августа я успешно стажировался в этой компании.&lt;/p&gt;

&lt;p&gt;График был примерно такой: я сдвинул свой рабочий день где-то на 3 часа вперед, так чтобы иметь окно коммуникации с 17:00 до 20:00-20:30 (с 10:00 до 13:00 по Москве), соответственно, все занятия, которые проводились с остальными стажерами в офисе утром - для меня были вечером. Проблем с изменением рабочего графика не возникло, продуктивность не пострадала, даже скорее наоборот - активное время увеличилось с 8 до 11 часов, что позволяло более гибко планировать день.&lt;/p&gt;

&lt;p&gt;По самому обучению - все очень круто. Начали с азов, с лекций и практических заданий (Docker, Chef, Ansible, Jenkins), потом переключились на реальные задачи. К концу стажировки я самостоятельно успешно возился с собственным тестовым кластером Kubernetes, занимаясь сравнительным исследованием актуальных систем оркестрации контейнеров. Была возможность поучаствовать и в opensource разработках, и в жизни комьюнити (я вот, например, занялся постингом новостей в Telegram-канал &lt;a href=&quot;https://telegram.me/devops_deflope&quot;&gt;DevOps Deflope News&lt;/a&gt; и планирую продолжать это и дальше). В Экспресс42 очень сильная инженерная культура, внутренние митапы, презентации, взаимопомощь. Были созданы все условия для обучения и развития, даже с поправкой на то, что я общался с командой всего 3 часа в день.&lt;/p&gt;

&lt;p&gt;К сожалению, после окончания стажировки, продолжить работу в Экспресс42 не получилось - вся команда и вся работа сосредоточена в Москве и нормально взаимодействовать с клиентами с моей разницей во времени было бы очень проблематично. Тем не менее, ребята порекомендовали меня в другую команду, куда я успешно прошел собеседование (рекордные полтора часа от “здравствуйте, мне тут сказали вы ищете работу” до “поздравляем, вы приняты”) и с сегодняшнего дня приступаю к работе. Команда полностью распределенная, задачи интересные, так что фана, думаю, будет еще больше чем на стажировке.&lt;/p&gt;

&lt;p&gt;Вообще, главный вывод для себя я сделал такой: надо пытаться устроиться на интересную тебе работу даже если боишься что не дотянешь по навыкам. Если есть желание и способность учиться - то дотянешь. Ну и для людей с небольших городов - remote-рынок сейчас бурно растет, вполне можно найти работу в Москве, сидя в Хабаровском крае. :)&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Aug 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/notes/devops/2016/08/15/career-changes.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/notes/devops/2016/08/15/career-changes.html</guid>
        
        
        <category>notes</category>
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>Как установить плагин в Roundcube</title>
        <description>&lt;p&gt;Задача нетривиальная для пользователей, как ни странно.&lt;/p&gt;

&lt;p&gt;Плагины ставятся с помощью composer – менеджера зависимостей для PHP. То есть, фактически, мы просто добавляем к текущей инсталляции Roundcube новую зависимость.&lt;/p&gt;

&lt;p&gt;Для начала, перейдем в директорию Roundcube и установим сам composer:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /var/www/roundcube
curl -s https://getcomposer.org/installer | php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Скопируем шаблон конфига композера для Roundcube и откроем его для редактирования:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cp composer.json-dist composer.json
vim composer.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Идем в &lt;a href=&quot;https://plugins.roundcube.net/&quot;&gt;https://plugins.roundcube.net/&lt;/a&gt;, ищем там нужный нам плагин, например &lt;a href=&quot;https://plugins.roundcube.net/packages/roundcube/filters&quot;&gt;https://plugins.roundcube.net/packages/roundcube/filters&lt;/a&gt;, добавляем строчку из описания плагина, следующую после require (например &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require: &quot;roundcube/filters&quot;: &quot;dev-master&quot;&lt;/code&gt;) в секцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; нашего &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer.json&lt;/code&gt;. В итоге она начинает выглядеть так (не забываем про запятые):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &quot;require&quot;: {
        &quot;php&quot;: &quot;&amp;gt;=5.3.7&quot;,
        &quot;roundcube/plugin-installer&quot;: &quot;~0.1.6&quot;,
        &quot;pear-pear.php.net/auth_sasl&quot;: &quot;~1.0.6&quot;,
        &quot;pear-pear.php.net/net_idna2&quot;: &quot;~0.1.1&quot;,
        &quot;pear-pear.php.net/mail_mime&quot;: &quot;~1.10.0&quot;,
        &quot;pear-pear.php.net/net_smtp&quot;: &quot;~1.7.1&quot;,
        &quot;pear-pear.php.net/crypt_gpg&quot;: &quot;~1.4.0&quot;,
        &quot;roundcube/net_sieve&quot;: &quot;~1.5.0&quot;,
        &quot;roundcube/filters&quot;: &quot;dev-master&quot;
    },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Сохраняем файл, закрываем редактор, запускаем установку зависимостей: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php composer.phar install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Композер выкачивает их, собирает, устанавливает, и т.д. По окончанию процесса он предложит добавить плагины в config/config.inc.php автоматически. Если не хотите, то можно сделать это вручную, добавив название плагина в параметр &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config[&apos;plugins&apos;]&lt;/code&gt; в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/config.inc.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$config[&apos;plugins&apos;] = array(
        &apos;acl&apos;,
        &apos;jqueryui&apos;,
        &apos;filters&apos;,
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Неприятный момент&lt;/strong&gt;. Если вы используете PHP 5.3 (который как бы нормально поддерживается Roundcube), то ничерта у вас не соберется со стандартным composer.phar. Причина – net_smtp последней версии (1.7.1 на данный момент) требует PHP не ниже 5.4.0. Выход: заменить строчку&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;pear-pear.php.net/net_smtp&quot;: &quot;~1.7.1&quot;,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;на&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;pear-pear.php.net/net_smtp&quot;: &quot;~1.6.3&quot;,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ну или обновиться до PHP 5.4+, т.к. 5.3 уже не поддерживается уже почти два года (5.4, впрочем, тоже).&lt;/p&gt;

&lt;h2 id=&quot;полезные-ссылки&quot;&gt;Полезные ссылки:&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://getcomposer.org/&quot;&gt;https://getcomposer.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://plugins.roundcube.net/&quot;&gt;https://plugins.roundcube.net/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 23 Feb 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/php/2016/02/23/roundcube-plugin-install.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/php/2016/02/23/roundcube-plugin-install.html</guid>
        
        
        <category>php</category>
        
      </item>
    
      <item>
        <title>Как избавиться от WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! при работе с Vagrant</title>
        <description>&lt;p&gt;При разработке и тестировании часто приходится пересоздавать Vagrant-машину с нуля. Так как айпишник у нее не меняется, после пересоздания машины и при попытке к ней зацепиться мы получаем истерику от SSH:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ssh root@192.168.99.99
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
9f:56:58:8c:08:bc:1a:b1:3f:fa:c8:2e:0a:97:6d:19.
Please contact your system administrator.
Add correct host key in /home/strangeman/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/strangeman/.ssh/known_hosts:340
 remove with: ssh-keygen -f &quot;/home/strangeman/.ssh/known_hosts&quot; -R 192.168.99.99
ECDSA host key for 192.168.99.99 has changed and you have requested strict checking.
Host key verification failed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Решается эта проблема просто: мы начинаем использовать /dev/null для хранения KnownHosts и отключаем строгую проверку ключа. Естественно, в целях безопасности, стоит это делать только для IP ваших Vagrant-машин (я, например, использую 192.168.99.0/24). Добавляем в начало &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/config&lt;/code&gt; следующие строки:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host 192.168.99.*
 StrictHostKeyChecking no
 UserKnownHostsFile=/dev/null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 20 Jan 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/hints/2016/01/20/vagrant-disable-strict-ssh.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/hints/2016/01/20/vagrant-disable-strict-ssh.html</guid>
        
        
        <category>hints</category>
        
      </item>
    
      <item>
        <title>Полезные команды, которые я постоянно забываю</title>
        <description>&lt;h2 id=&quot;tar--mysql-backup&quot;&gt;Tar &amp;amp; Mysql backup&lt;/h2&gt;

&lt;h3 id=&quot;tar-a-directory&quot;&gt;Tar a directory&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;czf name_of_archive_file.tar.gz name_of_directory_to_tar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;gzip-mysql-dump&quot;&gt;Gzip mysql dump&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysqldump admin_sdfsdf &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; admin_sgerfgdfg &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;gzip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; mysql.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;untar-a-directory&quot;&gt;Untar a directory&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-zxvf&lt;/span&gt; ../tmp/site.tar.gz &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;unzip-mysql-dump&quot;&gt;Unzip mysql dump&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zcat ../tmp/mysql.tar.gz | mysql sdfsdfsdf &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; sdfsdfsdfsdfsf &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;docker&quot;&gt;Docker&lt;/h2&gt;

&lt;h3 id=&quot;delete-all-containers&quot;&gt;Delete all containers&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker ps &lt;span class=&quot;nt&quot;&gt;-aq&lt;/span&gt; | xargs docker &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;delete-all-untagged-images&quot;&gt;Delete all untagged images&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker images | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;none&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker rmi &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;docker images | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;none&amp;gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{print &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;3}&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;kill-all-java-containers&quot;&gt;Kill all java containers&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker ps | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;java -&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt; | xargs &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; docker &lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;systemd&quot;&gt;systemd&lt;/h2&gt;

&lt;h3 id=&quot;restart-all-failed-units&quot;&gt;Restart all failed units&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl list-units &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;failed | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; ● | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 2 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt; |  xargs &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; systemctl restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 01 Jan 2016 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/hints/bash/2016/01/01/hints.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/hints/bash/2016/01/01/hints.html</guid>
        
        
        <category>hints</category>
        
        <category>bash</category>
        
      </item>
    
      <item>
        <title>Типовой конфиг сайта на связке NGINX + PHP-FPM</title>
        <description>&lt;p&gt;Пути файлов в зависимости от ОС, версии ОС и используемого софта (web-панели типа WHM или Parallels Plesk) могут очень сильно варьироваться, поэтому я их тут не указываю. Опции, в зависимости от конкретного сайта и сервера (особенно это касается PHP-FPM: используемого process manager и его настроек) также могут варьироваться в широких пределах. Здесь приведен более-менее дефолтный конфиг, с которым все должно “просто работать”, а производительность сервера можно тюнить уже потом.&lt;/p&gt;

&lt;p&gt;Установки я не касаюсь ровно по той же причине. Если с nginx чаще всего все довольно просто, то с PHP-FPM каждый случай индивидуален в зависимости от используемого софта.&lt;/p&gt;

&lt;h2 id=&quot;nginx-vhost&quot;&gt;NGINX Vhost&lt;/h2&gt;
&lt;p&gt;Возможные пути:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/etc/nginx/sites-enabled/example.conf
/etc/nginx/conf.d/example.conf
/etc/nginx/conf/example.conf
/etc/nginx/plesk.conf.d/vhosts/example.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;можно попробовать посмотреть командой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx -V&lt;/code&gt;, флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--conf-path&lt;/code&gt;, затем уже смотреть инклуды в основном конфиге.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
    listen 80;

    root /srv/example.com;
    server_name example.com;

    error_log /var/log/nginx/example.com.error_log;
    access_log /var/log/nginx/example.com.access_log;

    index index.php;

    location = /favicon.ico {
   log_not_found off;
            access_log off;
    }

    location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
    }

    location / {
            # This is cool because no php is touched for static content.
            # include the &quot;?$args&quot; part so non-default permalinks doesn&apos;t break when using query string
            try_files $uri $uri/ /index.php?$args;

    }

    location ~ \.php$ {
         # Filter out arbitrary code execution
        location       ~ \..*/.*\.php$ {return 404;}

        fastcgi_index index.php;
        include        fastcgi_params;
                fastcgi_pass unix:/var/run/php5-fpm.example.sock;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
    }

    location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar|swf)$ {
            expires max;
            log_not_found off;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;php-fpm-pool-definition&quot;&gt;PHP-FPM pool definition&lt;/h2&gt;
&lt;p&gt;Возможные пути:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/etc/php5/fpm/pool.d/example.conf
/etc/php-fpm/pool.d/example.conf
/etc/php5-fpm/pool.d/example.conf
/usr/local/etc/php-fpm.conf
/usr/local/etc/php5/fpm/pool.d/example.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;; Start a new pool named &apos;example&apos;.
; the variable $pool can we used in any directive and will be replaced by the
; pool name (&apos;example&apos; here)
[example]

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user&apos;s group
;   will be used.
user = www-data
group = www-data

listen = /var/run/php5-fpm.example.sock

listen.owner = www-data
listen.group = www-data

pm = ondemand
pm.max_children = 20

; Chdir to this directory at the start.
; Note: relative path can be used.
; Default Value: current directory or / when chroot
chdir = /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sun, 25 Oct 2015 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/nginx/php/2015/10/25/typical-nginx.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/nginx/php/2015/10/25/typical-nginx.html</guid>
        
        
        <category>nginx</category>
        
        <category>php</category>
        
      </item>
    
      <item>
        <title>Автоматическая настройка Nginx+PHP-FPM в VestaCP</title>
        <description>&lt;p&gt;Неактуально по причине выхода новой Весты, которая поддерживает PHP-FPM из коробки.&lt;/p&gt;

&lt;h2 id=&quot;пререквизиты&quot;&gt;Пререквизиты:&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Ubuntu 14.04&lt;/li&gt;
  &lt;li&gt;VestaCP 0.9.8&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Хочется, чтобы при добавлении домена через веб-морду VestaCP автоматически создавался пул PHP-FPM.&lt;/p&gt;

&lt;p&gt;TODO: Реализовать удаление пула из конфига при удалении домена. :)&lt;/p&gt;

&lt;h2 id=&quot;for-nginx&quot;&gt;For nginx&lt;/h2&gt;

&lt;p&gt;Основано вот на этом: https://github.com/autronix/Vesta-0.9.8-with-nginx-only&lt;/p&gt;

&lt;p&gt;Но я внес кое-какие правки под себя:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# cat /usr/local/vesta/data/templates/web/nginx/default.tpl

server {
    listen      %proxy_port%;
    server_name %domain_idn% %alias_idn%;
    error_log  /var/log/apache2/domains/%domain%.error.log error;

    root           %docroot%;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    include %home%/%user%/conf/web/nginx.%domain%.conf*;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass  unix:/var/run/php5-fpm.%domain_idn%.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht    {return 404;}
    location ~ /\.svn/  {return 404;}
    location ~ /\.git/  {return 404;}
    location ~ /\.hg/   {return 404;}
    location ~ /\.bzr/  {return 404;}

}

# cat /usr/local/vesta/data/templates/web/nginx/default.stpl

server {
    listen      %proxy_ssl_port%;
    server_name %domain_idn% %alias_idn%;
    ssl         on;
    ssl_certificate      %ssl_pem%;
    ssl_certificate_key  %ssl_key%;
    error_log  /var/log/apache2/domains/%domain%.error.log error;

    root           %docroot%;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    include %home%/%user%/conf/web/nginx.%domain%.conf*;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass  unix:/var/run/php5-fpm.%domain_idn%.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht    {return 404;}
    location ~ /\.svn/  {return 404;}
    location ~ /\.git/  {return 404;}
    location ~ /\.hg/   {return 404;}
    location ~ /\.bzr/  {return 404;}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;for-php-fpm&quot;&gt;For PHP-FPM&lt;/h2&gt;

&lt;p&gt;Устанавливаем php5-fpm&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apt install php5-fpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Прописываем, где искать инклуды конфигов:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# tail -n 1 /etc/php5/fpm/php-fpm.conf
include=/home/*/conf/web/php_pool.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Прописываем шаблон для конфига:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# cat /usr/local/vesta/data/templates/web/nginx/phpfpm.tpl
 
[%domain_idn%]
 
user = %user%
group = %group%
 
listen = /var/run/php5-fpm.$pool.sock
 
listen.owner = %user%
listen.group = %group%
;listen.mode = 0660
 
; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives. With this process management, there will be
;             always at least 1 children.
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in &apos;idle&apos;
;                                    state (waiting to process). If the number
;                                    of &apos;idle&apos; processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in &apos;idle&apos;
;                                    state (waiting to process). If the number
;                                    of &apos;idle&apos; processes is greater than this
;                                    number then some children will be killed.
;  ondemand - no children are created at startup. Children will be forked when
;             new requests will connect. The following parameter are used:
;             pm.max_children           - the maximum number of children that
;                                         can be alive at the same time.
;             pm.process_idle_timeout   - The number of seconds after which
;                                         an idle process will be killed.
; Note: This value is mandatory.
pm = ondemand
 
pm.max_children = 20
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.process_idle_timeout = 10s
pm.max_requests = 500
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Вносим правку в скрипт Vesta CP для добавления нового веб-прокси&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# diff -p /usr/local/vesta/bin/v-add-web-domain-proxy /usr/local/vesta_fixed/bin/v-add-web-domain-proxy -c5
*** /usr/local/vesta/bin/v-add-web-domain-proxy    2015-06-05 02:00:50.000000000 +1000
--- /usr/local/vesta_fixed/bin/v-add-web-domain-proxy    2015-10-15 10:45:52.162316790 +1000
*************** if [ &quot;$SSL&quot; = &apos;yes&apos; ]; then
*** 84,93 ****
--- 84,100 ----
      if [ -z &quot;$(grep &quot;$conf&quot; $proxy_conf)&quot; ]; then
          echo &quot;include $conf;&quot;&amp;amp;gt;&amp;amp;gt; $proxy_conf
      fi
  fi
 
+ # Add PHP-fpm pool
+ tpl_file=&quot;$WEBTPL/$PROXY_SYSTEM/phpfpm.tpl&quot;
+ conf=&quot;$HOMEDIR/$user/conf/web/php_pool.conf&quot;
+ add_web_config
+ service php5-fpm restart
+
+
  # Running template trigger
  if [ -x $WEBTPL/$PROXY_SYSTEM/$template.sh ]; then
      $WEBTPL/$PROXY_SYSTEM/$template.sh $user $domain $ip $HOMEDIR $docroot
  fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Важный нюанс: sock-файл для общения между Nginx и PHP-FPM у нас создается с правами 0660 от имени пользователя. Соответственно Nginx должен запускаться от пользователя, состоящего в той же группе, что и пользователь (ну или наоборот, пользователь должен быть быть в группе nginx).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Собственно все. Рестартим Vesta, Nginx, PHP-FPM, добавляем новый домен через веб-морду, проверяем.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service vesta restart
service php5-fpm restart
service nginx restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 20 Oct 2015 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/nginx/php/2015/10/20/nginx-php-fpm-vestacp.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/nginx/php/2015/10/20/nginx-php-fpm-vestacp.html</guid>
        
        
        <category>nginx</category>
        
        <category>php</category>
        
      </item>
    
      <item>
        <title>Markdown Cheatsheet</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is Markdown Cheatsheet Demo for &lt;strong&gt;Thinkspace&lt;/strong&gt;, this Jekyll theme. Please check the raw content of this file for the markdown usage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;typography-elements-in-one&quot;&gt;Typography Elements in One&lt;/h2&gt;

&lt;p&gt;Let’s start with a informative paragraph. &lt;strong&gt;This text is bolded.&lt;/strong&gt; But not this one! &lt;em&gt;How about italic text?&lt;/em&gt; Cool right? Ok, let’s &lt;strong&gt;&lt;em&gt;combine&lt;/em&gt;&lt;/strong&gt; them together. Yeah, that’s right! I have code to highlight, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThisIsMyCode()&lt;/code&gt;. What a nice! Good people will hyperlink away, so &lt;a href=&quot;#&quot;&gt;here we go&lt;/a&gt; or &lt;a href=&quot;http://www.example.com&quot;&gt;http://www.example.com&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;headings-h1-to-h6&quot;&gt;Headings H1 to H6&lt;/h2&gt;

&lt;h1 id=&quot;h1-heading&quot;&gt;H1 Heading&lt;/h1&gt;

&lt;h2 id=&quot;h2-heading&quot;&gt;H2 Heading&lt;/h2&gt;

&lt;h3 id=&quot;h3-heading&quot;&gt;H3 Heading&lt;/h3&gt;

&lt;h4 id=&quot;h4-heading&quot;&gt;H4 Heading&lt;/h4&gt;

&lt;h5 id=&quot;h5-heading&quot;&gt;H5 Heading&lt;/h5&gt;

&lt;h6 id=&quot;h6-heading&quot;&gt;H6 Heading&lt;/h6&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;footnote&quot;&gt;Footnote&lt;/h2&gt;

&lt;p&gt;Let’s say you have text that you want to refer with a footnote, you can do that too! This is an example for the footnote number one [&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;]. You can even add more footnotes, with link! [&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;]&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;blockquote&quot;&gt;Blockquote&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Start by doing what’s necessary; then do what’s possible; and suddenly you are doing the impossible. –Francis of Assisi&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This theme does NOT support nested blockquotes.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;list-items&quot;&gt;List Items&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;First order list item&lt;/li&gt;
  &lt;li&gt;Second item&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;Unordered list can use asterisks&lt;/li&gt;
  &lt;li&gt;Or minuses&lt;/li&gt;
  &lt;li&gt;Or pluses&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;code-blocks&quot;&gt;Code Blocks&lt;/h2&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;JavaScript syntax highlighting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Python syntax highlighting&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;No language indicated, so no syntax highlighting.
But let&apos;s throw in a &amp;lt;b&amp;gt;tag&amp;lt;/b&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;table&quot;&gt;Table&lt;/h2&gt;

&lt;h3 id=&quot;table-1-with-alignment&quot;&gt;Table 1: With Alignment&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tables&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Are&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Cool&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;col 3 is&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;right-aligned&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$1600&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;col 2 is&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;centered&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;zebra stripes&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;are neat&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;$1&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;table-2-with-typography-elements&quot;&gt;Table 2: With Typography Elements&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Markdown&lt;/th&gt;
      &lt;th&gt;Less&lt;/th&gt;
      &lt;th&gt;Pretty&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;em&gt;Still&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;renders&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;nicely&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;horizontal-line&quot;&gt;Horizontal Line&lt;/h2&gt;

&lt;p&gt;The HTML &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; element is for creating a “thematic break” between paragraph-level elements. In markdown, you can create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; with any of the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;___&lt;/code&gt;: three consecutive underscores&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;---&lt;/code&gt;: three consecutive dashes&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;***&lt;/code&gt;: three consecutive asterisks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;renders to:&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

&lt;hr /&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;media&quot;&gt;Media&lt;/h2&gt;

&lt;h3 id=&quot;youtube-embedded-iframe&quot;&gt;YouTube Embedded Iframe&lt;/h3&gt;

&lt;div class=&quot;video-container&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/n1a7o44WxNo&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h3 id=&quot;image&quot;&gt;Image&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://octodex.github.com/images/minion.png&quot; alt=&quot;Minion&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;Footnote:&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;1: Footnote number one yeah baby! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;2: A footnote you can link to - &lt;a href=&quot;#&quot;&gt;click here!&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
        <link>https://blog.strangeman.info/hints/1970/01/01/markdown-cheatsheet.html</link>
        <guid isPermaLink="true">https://blog.strangeman.info/hints/1970/01/01/markdown-cheatsheet.html</guid>
        
        
        <category>hints</category>
        
      </item>
    
  </channel>
</rss>
