<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title/><link>https://amir.goodarzi.net/</link><description>Amir Goodarzi blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright><lastBuildDate>Wed, 16 Oct 2024 13:25:00 +0200</lastBuildDate><atom:link href="https://amir.goodarzi.net/index.xml" rel="self" type="application/rss+xml"/><item><title>TIL: Using git restore to Revert File Changes and Sync with Another Branch</title><link>https://amir.goodarzi.net/til-16-10-2024-git-restore/</link><pubDate>Wed, 16 Oct 2024 13:25:00 +0200</pubDate><author>Amir</author><guid>https://amir.goodarzi.net/til-16-10-2024-git-restore/</guid><description><![CDATA[<blockquote>
<p><strong>Today I learned</strong> that if you need to revert a specific file to its original
state, even after commits, or if you want to sync a file with another branch,
you can use the <code>git restore</code> command.</p>
</blockquote>
<p>The <code>git restore</code> command allows you to revert a file to its previous state or sync it with a different branch. This can be particularly useful when you need to discard changes and align a file with its version from another branch, such as main.</p>
<p>In this example, I want to restore and discard all committed changes in my current branch for a specific file to match the version from the main branch:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git restore --source<span class="o">=</span>main -- main.tf</span></span></code></pre></div></div>
<ul>
<li>git-restore <a href="https://git-scm.com/docs/git-restore" target="_blank" rel="noopener noreffer ">documentation</a></li>
<li>Cover image is licensed with <a href="https://creativecommons.org/licenses/by/3.0/deed.en" target="_blank" rel="noopener noreffer "> Creative Commons Attribution 3.0
Unported</a>
and <a href="https://commons.wikimedia.org/wiki/File:Git-logo.svg" target="_blank" rel="noopener noreffer ">source</a></li>
</ul>
]]></description></item><item><title>Create Docker images secure and fast with Kaniko</title><link>https://amir.goodarzi.net/kaniko/</link><pubDate>Thu, 10 Feb 2022 03:29:25 +0330</pubDate><author>Amir</author><guid>https://amir.goodarzi.net/kaniko/</guid><description><![CDATA[<h2 id="the-intro">The intro</h2>
<h3 id="what-is-kaniko">what is Kaniko</h3>
<p><a href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noopener noreffer ">Kaniko</a> is simply a Docker
image builder. You may not be satisfied with this introduction, but this is what
Kaniko is all about. Build images and push them to a registry from Dockerhub,
ECR, ACR or your own container image registry.</p>
<h3 id="how-does-it-work">How does it work?</h3>
<p>It&rsquo;s super easy to use. Extract the base image file system first. Run each of
the instructions (COPY, ADD, RUN) into the base image&rsquo;s file system. It would
create a snapshot of the current state of the file system, add newly added or
changed files to the base image, and update the image metadata. Quite useful.
No? Not until you learn how to use it.</p>
<h3 id="use-cases">Use cases</h3>
<p>Kaniko creates <strong>Docker images</strong> inside a container or Kubernetes. If you face
any of these issues, then this article will help you to mitigate them:</p>
<ul>
<li>
<p>If you are building images on a shared machine or runner, you may be able to
reduce the risk of security breaches. There are several methods for building
images. You might be able to write some bash scripts, Ansible playbooks, or
some sort of script into your CI/CD provider to do something like <code>docker build -t blahblahblah:v0.0.1 -f SomeSortOfDockerfile</code>. You can run this
command from your bash or from a container (Docker in Docker). Neither of them
is suitable for shared environments. First, imagine a scenario where another
user/developer/pipeline gains access to your code, or you need to access some
critical data from a database, or maybe you need to get some critical
credentials (or keys) from another source. This could lead to a disaster. Your
code could be accessed by others, your database data could be leaked, or
someone could steal your keys. This is the worst nightmare for a security and
operations team.</p>
</li>
<li>
<p>Reduced build time is another feature you may be interested in. Servers
(runners) are sometimes a problem for DevOps teams due to the long-running
time of a build process. You may have a bad day if your codebase is huge and
you have many dependencies to get from the internet.
No one (I mean the DevOps or SRE teams) wants to get another ticket from the Dev
team about &ldquo;our build pipelines are slow. we are on a force and hotfixes should
be merged into production ASAP&rdquo;.</p>
</li>
<li>
<p>Perhaps you have a limited budget for infrastructure, so you rely completely
on your Kubernetes cluster. From the database to the environment for building
Docker images. (Pro tips: Do not deploy anything on Kubernetes. In the future,
I will write about it. Databases could be run on Kubernetes, but don&rsquo;t do
that)</p>
</li>
</ul>
<p>We have <strong>Kaniko</strong> to fix these issues. Although it&rsquo;s not a superhero, it can
make us the company&rsquo;s superhero.</p>
<h2 id="the-issue">The issue</h2>
<h3 id="what-we-had">What we had</h3>
<p>I will use this opportunity to share how our CI/CD infrastructure in <a href="https://www.linkedin.com/company/alibaba-travels/" target="_blank" rel="noopener noreffer ">Alibaba
Travels</a> was deployed and
what we did to make it more reliable.  This was an old infrastructure that
needed to be renewed or restructured. We have just migrated several projects to
this new structure into the staging, dev and other environment other than
production but there are still many more that must be done.</p>
<p>Returning to the main topic, we have projects from different stacks. We use
everything from Dotnet Core to Nodejs and Python. Previously, all projects used
a shared runner, which ran on Gitlab Continuous Integration. There are several
shell and Docker runners installed on that beefy server (to reduce failure
risks, they are still available but we are planning to migrate them). These are
the types of runners used by the projects. Obviously, projects that run build
process on Docker will have access to the Docker socket, and on pipelines, all
people within that runner can see each other and even grant shell access to
another container.</p>
<h3 id="the-danger-zone">The danger zone</h3>
<p>This could lead to reading some data and maybe codes. Shell runners are also
available. They provide shell access to the runner and can do whatever they want
to the gitlab-runner user. Several reasons led us to move our runners to
Kubernetes. Better control of each project&rsquo;s resources, better control of the
network, isolation between containers, and more.</p>
<h3 id="a-step-further">A step further</h3>
<p>After we create several Kubernetes runners, everything works fine, but there is
another issue. It is not possible to mount the Docker socket of the host into
the container. It is too risky. The container will gain access to the host&rsquo;s
container. There are some of you who said that&rsquo;s ok, we can create dedicated
machines to run on (which is a costly solution). Can you mount a single Docker
socket to multiple containers? Additionally, you have to deal with security
context, which creates more holes in the system.</p>
<p><strong>This is the disaster.</strong></p>
<h2 id="the-solution">The Solution</h2>
<h3 id="what-we-need">What we need</h3>
<p>Kaniko helps us to close these gaps. After implementing the Kaniko, it took me
21 minutes to build a Docker image from a DotNet application. The process takes
three and a half minutes without the Kubernetes runner. For a Node.js
application, this time increased to 45 minutes. On our shared runners, it takes
11 minutes.</p>
<h3 id="what-we-do">What we do</h3>
<p>We want it to be more reliable, but what we do takes much longer.</p>
<h4 id="caching">Caching</h4>
<p>Caching helps me to fix it. Caching should be enabled first (<code>--cache=true</code>
flag). We have two choices for caching.</p>
<ul>
<li>cache into a local directory (<code>--cache-dir</code>)
Cached into a specified repository (<code>--cache-repo</code> flag)
Since we have a lot of resources on our Registry, we decided to cache them</li>
</ul>
<p>Not to forget that COPY and RUN instructions can be cached when you enable the
<code>--cache-copy-layers=true</code> flag.
Note 2: These two caching methods will become available when you set <code>--cache=true</code></p>
<h4 id="dockerfile">Dockerfile</h4>
<p>Check out this Dockerfile. Everything seems to be okay, and the developer is
happy with it.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-dockerfile">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt update <span class="o">&amp;&amp;</span> apt install libgnutls30 <span class="o">&amp;&amp;</span> apt install ca-certificates <span class="o">&amp;&amp;</span> update-ca-certificates<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_ENVIRONMENT</span><span class="o">=</span>Production
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_URLS</span><span class="o">=</span>http://+:80<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet restore   src/app/app.csproj --configfile src/app/nuget.config<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet publish src/app/apptifier.csproj -c Release -o /app/out<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>build-env /app/out .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENTRYPOINT</span> <span class="p">[</span><span class="s2">&#34;dotnet&#34;</span><span class="p">,</span><span class="s2">&#34;app.dll&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<p>It&rsquo;s pretty normal, but I think this is another <strong>disaster</strong>. Let me explain:</p>
<ul>
<li>COPY instructions should be divided into smaller parts. In this case I changed
to copy every module of the application.</li>
<li>It is possible to merge two RUN instructions.</li>
<li>The apt command does not delete cached packages. This is not important in this
situation, but we will do it anyway.</li>
</ul>
<p>Reducing the number of lines in the Dockerfile does not work for Kaniko. Don&rsquo;t
be afraid to write more lines. Here is what I changed it to:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-dockerfile">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build-env</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_ENVIRONMENT</span><span class="o">=</span>Production
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_URLS</span><span class="o">=</span>http://+:80<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/Module1 ./src/Module1<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/Module2 ./src/Module2<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/app ./src/app<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> tests/app.Test ./tests/Iapp.Test.Test<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> tests/app.integrity.Test ./tests/app.integrity.Test<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> app.sln ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet publish --configfile src/app/nuget.config src/app/app.csproj -c Release -o /app/out --nologo -verbosity:quiet<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>build-env /app/out .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">entrypoint</span> <span class="p">[</span><span class="s2">&#34;dotnet&#34;</span><span class="p">,</span><span class="s2">&#34;app.dll&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<p>I used the same method for other projects as well.</p>
<h4 id="snapshot">Snapshot</h4>
<p>In the beginning of my post, I mentioned that Snapshots are a method of storing
layers of images and their states. With <code>--single-snapshot=true</code>, you can only
take a snapshot at the end of the process, which is super fast, but not cache
friendly, or you can use <code>--snapshotMode</code> to ensure the proper snapshot method
is used. I just copied and pasted the snapshotmone arguments from the official
document to demonstrate how it works.</p>
<table>
	<thead>
			<tr>
					<th>Mode</th>
					<th>result</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>full</td>
					<td>The full file contents and metadata are considered when snapshotting. This is the least performant option, but also the most robust.</td>
			</tr>
			<tr>
					<td>redo</td>
					<td>he file mtime, size, mode, owner uid and gid will be considered when snapshotting. This may be up to 50% faster than &ldquo;full&rdquo;, particularly if your project has a large number files.</td>
			</tr>
			<tr>
					<td>time</td>
					<td>only file mtime will be considered when snapshotting</td>
			</tr>
	</tbody>
</table>
<p>for some reason I choose redo.</p>
<h2 id="result">Result</h2>
<p>First, it took a little bit longer than the original <code>docker build</code> command,
which is understandable. Kaniko attempts to create caches.
However, after making a few changes to the modules and files, the results are
stunning.
Here&rsquo;s a look at the table:</p>
<table>
	<thead>
			<tr>
					<th>Project type</th>
					<th>docker build time</th>
					<th>Kaniko without caching</th>
					<th>Kaniko first time caching</th>
					<th>Kaniko Second time with caching</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Dotnet core</td>
					<td>3:10</td>
					<td>14:25</td>
					<td>3:40</td>
					<td>0:27</td>
			</tr>
			<tr>
					<td>NodeJS</td>
					<td>11:45</td>
					<td>45:20</td>
					<td>13:10</td>
					<td>2:10</td>
			</tr>
	</tbody>
</table>
<h3 id="using-kaniko">Using Kaniko</h3>
<p>There are four things you need to use Kaniko</p>
<ul>
<li>A Dockerfile and source code called build context, along with a little tweak
to your Dockerfile</li>
<li>a registry that you push to</li>
<li>Kaniko</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko#using-kaniko" target="_blank" rel="noopener noreffer ">Kaniko documents</a></li>
</ul>
<h4 id="my-method">My Method</h4>
<p>Here is the complete command I use:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-shell">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">        /kaniko/executor
</span></span><span class="line"><span class="cl">        --context <span class="s2">&#34;projectDirectory&#34;</span>
</span></span><span class="line"><span class="cl">        --dockerfile <span class="s2">&#34;</span><span class="nv">$projectDirectory</span><span class="s2">/Dockerfile&#34;</span>
</span></span><span class="line"><span class="cl">        --destination <span class="s2">&#34;imageTag:version&#34;</span>
</span></span><span class="line"><span class="cl">        --cache-copy-layers<span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl">        --snapshotMode<span class="o">=</span><span class="nb">time</span>
</span></span><span class="line"><span class="cl">        --use-new-run
</span></span><span class="line"><span class="cl">        --cache<span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl">        --cache-repo<span class="o">=</span><span class="s2">&#34;imageTag:cache&#34;</span></span></span></code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<h3 id="notes">Notes</h3>
<h4 id="notes-on-prodction">Notes on Prodction</h4>
<p>This was applied to staging, development, and other environments other than
production. It may take up to six months to fully adopt this method and tools.
Therefore, if you wish to implement this, make sure you have tested everything
and no complex issues like security issues appear and harm your production.
Thus, it is a stable and perfect tool, Use at your own risk.</p>
<h4 id="other-notes">Other notes</h4>
<p>The goal of this post is to help you improve the performance and security of
your Docker build pipelines. Please feel free to leave any kind of comment.
Please let me know if there is any conflict or issue :)</p>
]]></description></item></channel></rss>