DEV Community: DevStream The latest articles on DEV Community by DevStream (@devstream). https://dev.to/devstream https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5437%2Fd2d142cd-712e-4091-8f99-58c4dc7d332c.png DEV Community: DevStream https://dev.to/devstream en App-Centric Configuration: Adding More Value to Your Life Tiexin Guo Wed, 30 Nov 2022 01:55:06 +0000 https://dev.to/devstream/app-centric-configuration-adding-more-value-to-your-life-8l0 https://dev.to/devstream/app-centric-configuration-adding-more-value-to-your-life-8l0 <h2> DevStream's Story </h2> <p><a href="proxy.php?url=https://github.com/devstream-io/devstream/releases/tag/v0.1.0">About nine months ago, DevStream was first publicly released</a>. Since then, it has evolved a lot. If this is the first time you have come across DevStream, maybe read <a href="proxy.php?url=https://blog.devstream.io/posts/hello-world/">this blog</a> for a quick overview.</p> <p>In the current incarnation (v0.9), every single DevOps tool (including integrations of tools and CI/CD pipeline setup for apps) is treated as a "Tool", which is a DevStream concept. So, if you would like to define a DevOps platform that does the following:</p> <ul> <li>repository scaffolding</li> <li>continuous integration w/ GitHub Actions</li> <li>install Argo CD as the tool for continuous deployment</li> <li>continuous deployment w/ Argo CD</li> </ul> <p>Your DevStream <code>tools.yaml</code> config file would look similar to this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">tools</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repo-scaffolding</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">myapp</span> <span class="na">options</span><span class="pi">:</span> <span class="na">destinationRepo</span><span class="pi">:</span> <span class="na">owner</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">githubUser</span> <span class="pi">]]</span> <span class="na">repo</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">app</span> <span class="pi">]]</span> <span class="na">branch</span><span class="pi">:</span> <span class="s">main</span> <span class="na">repoType</span><span class="pi">:</span> <span class="s">github</span> <span class="na">sourceRepo</span><span class="pi">:</span> <span class="na">org</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">githubUser</span> <span class="pi">]]</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">dtm-scaffolding-python</span> <span class="na">repoType</span><span class="pi">:</span> <span class="s">github</span> <span class="na">vars</span><span class="pi">:</span> <span class="na">ImageRepo</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">dockerUser</span> <span class="pi">]]</span><span class="s">/[[ app ]]</span> <span class="na">AppName</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">app</span> <span class="pi">]]</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">githubactions-python</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">default</span> <span class="na">dependsOn</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">repo-scaffolding.myapp</span> <span class="pi">]</span> <span class="na">options</span><span class="pi">:</span> <span class="na">owner</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">githubUser</span> <span class="pi">]]</span> <span class="na">repo</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">app</span> <span class="pi">]]</span> <span class="na">language</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">python</span> <span class="na">branch</span><span class="pi">:</span> <span class="s">main</span> <span class="na">docker</span><span class="pi">:</span> <span class="na">registry</span><span class="pi">:</span> <span class="na">type</span><span class="pi">:</span> <span class="s">dockerhub</span> <span class="na">username</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">dockerUser</span> <span class="pi">]]</span> <span class="na">repository</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">app</span> <span class="pi">]]</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">helm-installer</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">argocd</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">argocdapp</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">default</span> <span class="na">dependsOn</span><span class="pi">:</span> <span class="pi">[</span> <span class="s2">"</span><span class="s">argocd.default"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">githubactions-python.default"</span> <span class="pi">]</span> <span class="na">options</span><span class="pi">:</span> <span class="na">app</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">app</span> <span class="pi">]]</span> <span class="na">namespace</span><span class="pi">:</span> <span class="s">argocd</span> <span class="na">destination</span><span class="pi">:</span> <span class="na">server</span><span class="pi">:</span> <span class="s">https://kubernetes.default.svc</span> <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span> <span class="na">source</span><span class="pi">:</span> <span class="na">valuefile</span><span class="pi">:</span> <span class="s">values.yaml</span> <span class="na">path</span><span class="pi">:</span> <span class="s">helm/[[ app ]]</span> <span class="na">repoURL</span><span class="pi">:</span> <span class="s">${{repo-scaffolding.myapp.outputs.repoURL}}</span> </code></pre> </div> <p>A quick explanation - what DevStream does with this config is the following: </p> <ul> <li>install Argo CD;</li> <li>repository scaffolding for myapp;</li> <li>CI/CD for myapp.</li> </ul> <p>It looks nice and works like a charm.</p> <p>However, suppose you have more than one app to worry about (more often than not, that would be the case in the microservice era). In that case, things start to get a bit complicated: you'd have to repeat the <code>githubactions-golang</code> and <code>argocdapp</code> sections as many times as the number of apps you manage.</p> <p>That is to say, to manage ten apps/microservices, your DevStream YAML file may grow to well past 300 lines of config.</p> <p>Today, we are happy to announce that it's no longer the case. We've simplified it. By a big margin. With the release of v0.10.</p> <p>Read on.</p> <h2> Adding Value to Your Life </h2> <p>A big config is definitely harder to read and maintain, and we don't like that. That's why we decided to solve this problem in the first place.</p> <p>However, we waited to get right into it.</p> <p>Instead, we started to think about our market positioning and value proposition. What is DevStream anyway? What problems does it solve? Why would other engineers want to use it instead of building stuff manually or purchasing one-stop DevOps platforms?</p> <p>If we could precisely answer these questions, we would know how to improve it to the next level.</p> <p>The discussion went on and on. The whole team, including our CEO, discussed multiple days. We've put tens of hours of thought and debate into it, until we figured out where exactly DevStream can add value to your life:</p> <ul> <li>installing DevOps tools (but that's not the point at all)</li> <li>integrating DevOps tools and building engineering platforms (now we're talking)</li> <li>application lifecycle/software development lifecycle management: <ul> <li>repository bootstrapping</li> <li>continuous integration pipelines, integrating best practices, typical stages, and steps into it</li> <li>continuous deployment pipelines, integrating helm chart/Dockerfile best practices</li> <li>...</li> </ul> </li> </ul> <p><em>Note: read more on <a href="proxy.php?url=https://www.redhat.com/en/topics/devops/what-is-application-lifecycle-management-alm">Application Lifecycle Management (ALM) here</a> and <a href="proxy.php?url=https://medium.com/gitguardian/how-adding-security-into-devops-accelerates-the-sdlc-pt-1-459134252ee7">Software Development Lifecycle (SDLC) here</a>.</em></p> <h2> Lightbulb: Apps? Apps! </h2> <p>Since many values DevStream could bring are around applications/microservices and their lifecycle management, why not simply build configurations based on that?</p> <p>We call this new thought "app-centric" configuration, and let's get right into it:</p> <h2> Backward Compatibility </h2> <p>First things first, the previous "tools" config still works. We wouldn't want to break that. That is to say, if you copy-paste the first code snippet from the beginning of this article, it's supposed to work without any problem, just like previous versions. </p> <h2> (Much) Shorter, Simpler, Easier, While Achieving the Same </h2> <p>You'd be surprised that the config below does exactly the same thing as the code snippet at the very beginning of this article.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">apps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">myapp</span> <span class="na">spec</span><span class="pi">:</span> <span class="na">language</span><span class="pi">:</span> <span class="s">python</span> <span class="na">framework</span><span class="pi">:</span> <span class="s">django</span> <span class="na">repo</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/myapp</span> <span class="na">repoTemplate</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/dtm-scaffolding-python</span> <span class="na">ci</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">githubactions</span> <span class="na">cd</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">argocdapp</span> </code></pre> </div> <p>Is this magic? How'd we do that?</p> <p>First, we created a new abstraction that is called "apps". Each app corresponds to a real-world application or microservice that you manage.</p> <p>Secondly, we simplified the configurations as much as possible with five small sections:</p> <ul> <li>spec: language, framework related to the application, which is shared information with other sections</li> <li>repo: where to create/bootstrap the repository for the app</li> <li>repoTemplate: the template used to bootstrap the app's repo</li> <li>ci: setting up continuous integration for this app</li> <li>cd: setting up continuous deployment for this app</li> </ul> <p>Last but not least, more "defaults" and "best practices" are integrated, by default, into DevStream, so that you don't have to override most of the configs.</p> <p>Neat, right? I know.</p> <h2> One File to Rule Them All </h2> <p>Putting It All Together:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">config</span><span class="pi">:</span> <span class="na">state</span><span class="pi">:</span> <span class="na">backend</span><span class="pi">:</span> <span class="s">local</span> <span class="na">options</span><span class="pi">:</span> <span class="na">stateFile</span><span class="pi">:</span> <span class="s">devstream.state</span> <span class="na">tools</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">helm-installer</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">argocd</span> <span class="na">apps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">myapp1</span> <span class="na">spec</span><span class="pi">:</span> <span class="na">language</span><span class="pi">:</span> <span class="s">python</span> <span class="na">framework</span><span class="pi">:</span> <span class="s">django</span> <span class="na">repo</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/myapp1</span> <span class="na">repoTemplate</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/dtm-scaffolding-python</span> <span class="na">ci</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">githubactions</span> <span class="na">cd</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">argocdapp</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">myapp2</span> <span class="na">spec</span><span class="pi">:</span> <span class="na">language</span><span class="pi">:</span> <span class="s">golang</span> <span class="na">framework</span><span class="pi">:</span> <span class="s">gin</span> <span class="na">repo</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/myapp2</span> <span class="na">repoTemplate</span><span class="pi">:</span> <span class="na">url</span><span class="pi">:</span> <span class="s">github.com/devstream-io/dtm-scaffolding-golang</span> <span class="na">ci</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">githubactions</span> <span class="na">cd</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">argocdapp</span> </code></pre> </div> <p>I hope you enjoy this new feature. Have fun experimenting tools together with apps!</p> devops devstream platformengineering cloud DevStream 0.9.0 Release Tiexin Guo Fri, 16 Sep 2022 01:09:23 +0000 https://dev.to/devstream/devstream-090-release-hmd https://dev.to/devstream/devstream-090-release-hmd <h2> Official Releases for Different Platforms </h2> <ul> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.9.0/dtm-darwin-arm64">dtm-darwin-arm64</a></li> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.9.0/dtm-darwin-amd64">dtm-darwin-amd64</a></li> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.9.0/dtm-linux-amd64">dtm-linux-amd64</a></li> </ul> <h2> Major Changes since Last Release </h2> <p>This version focuses on two things:</p> <ul> <li>refactoring</li> <li>making the Jenkins/GitLab/Harbor/Java combination prod-ready</li> </ul> <p>Here we highlighted a few changes in this release:</p> <h3> Core </h3> <ul> <li>helm-type plugins now support local charts</li> <li>there is a new "force" flag to <code>dtm destroy</code> command, thanks to @csonezp</li> <li>k8s backend is supported for storing state</li> </ul> <h3> Plugins </h3> <ul> <li>GitHub Actions for Python is enhanced</li> <li>Jenkins/GitLab/Harbor plugins are greatly enhanced, including documentation, features, default values, etc.</li> <li>ArgoCD plugin now treat namespace in an "upsert" manner</li> <li>new plugin: harbor</li> <li>added many default options for each plugin</li> </ul> <h3> Docs </h3> <ul> <li>Install dtm with asdf in quickstart, thanks to @zhenyuanlau</li> <li>GitLab + Jenkins + Harbor On Premise Toolchain best practice (in Mandarin)</li> <li>There is a new document (in both English and Chinese) detailing how to setup a local development Environment for Golang/K8s related projects (not specific to DevStream)</li> </ul> <h2> More </h2> <p>See the <a href="proxy.php?url=https://github.com/devstream-io/devstream/compare/v0.8.0...v0.9.0">full changelog here</a>.</p> devstream devops tooling opensource DevStream Certification Program Introduction Tiexin Guo Thu, 21 Jul 2022 08:12:37 +0000 https://dev.to/devstream/devstream-certification-program-introduction-fkb https://dev.to/devstream/devstream-certification-program-introduction-fkb <p>Hello, and welcome to another DevStream blog post!</p> <p>In today's post, DevStream is happy to announce the new certification program for our contributors!</p> <h2> 0 Background </h2> <p>For veteran contributors dating back to the beginning of this year, I'm sure you still remember our original certification, which is a well-designed digital image that certifies your efforts in the open-source community.</p> <p>As beautiful as it is, there are some drawbacks with a certification in the form of digital images. To name a few:</p> <ol> <li>The image is pretty big, making it hard to share on social media platforms.</li> <li>Since it's an image, it's not so easy to embed it in your profile page, for example, LinkedIn profile, GitHub profile, or even inside your CV.</li> <li>It's not easy for others to verify the authenticity of the certification.</li> <li>Others don't know much about what you did to get that achievement, what skills you already mastered.</li> </ol> <h2> 1 Introducing the New Digital Certification Program </h2> <p>With those pain points in mind, we sought out a new form of credential/certification system that would address those very drawbacks, and it wasn't long before we got ourselves an answer: Credly.</p> <p>TL;DR: Credly helps issue digital badges.</p> <p>OK, that was brutally brief, isn't it? So, let's read the long version:</p> <h2> 2 <a href="proxy.php?url=https://info.credly.com/about-us">The Credly Story</a> </h2> <blockquote> <p>Credly was founded to help people connect their verified abilities to opportunities. And, we strive to bring equity and access to every member of the current and future workforce. After leading the transformation in how people learn and connect online, our team turned its sights on bringing innovation to the outputs of meaningful learning experiences: the credential itself.</p> <p>In 2018, Credly acquired the Acclaim credential business from Pearson and became the most comprehensive global solution for recognizing skills, capabilities, and achievements. Pearson invested in Credly’s growth and in 2022, acquired Credly to provide powerful solutions in the global workforce learning and talent market.</p> <p>Credly is leading the digital credential movement, making talent more visible and opportunity more accessible.</p> </blockquote> <p>Credly works with leading organizations that share our focus on unleashing the potential of the workforce with digital credentials.</p> <h2> 3 Why Did DevStream Go with Credly </h2> <p>No brainer: it seems <a href="proxy.php?url=https://learn.credly.com/blog/your-brand-doesn-t-need-to-white-label-digital-credentials">95% of the top IT certifications are issued through the Credly network</a>.</p> <p>Plus, I got to know Credly because <a href="proxy.php?url=https://www.credly.com/organizations/amazon-web-services/badges">AWS uses Credly for its certifications</a>. Have a quick look at this beautiful wall of AWS certifications you can get:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--VNGpuMqr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01fxh8hytgyat0l14jsg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--VNGpuMqr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01fxh8hytgyat0l14jsg.png" alt="-aws-certification" width="880" height="649"></a></p> <p>Checkout <a href="proxy.php?url=https://www.credly.com/users/tiexin-guo/badges">my profile</a> on Credly:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--xuMyC6ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6015lkr4dii9218uzb89.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--xuMyC6ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6015lkr4dii9218uzb89.png" alt="tiexin-guo-devstream-credly-profile" width="880" height="591"></a></p> <h2> 4 Wait a Minute. So I Got My Certs. Where Can I Show Them Off? </h2> <p>GitHub profile:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--cwHUubnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tntd2q1kdta7l49e7aj1.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--cwHUubnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tntd2q1kdta7l49e7aj1.png" alt="github-credly" width="880" height="531"></a></p> <p>LinkedIn profile:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--nj73b6sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5xgmgsodftjnu87thm1.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--nj73b6sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5xgmgsodftjnu87thm1.png" alt="linkedin-credly" width="880" height="1078"></a></p> <p>Tweet all you want, and DevStream will try our best to retreat your tweets!</p> <p>Spoil alert: DevStream is redesigning our <a href="proxy.php?url=https://www.devstream.io/community/">community page on our official website</a>. I know, I know, it looks ugly; for now. It will be pretty, soon. You have my words. Contributors and their certifications will show up on the community page. Think of it as a "wall of fame" of DevStream. How cool is that!</p> <h2> 5 Where Do We Go From Here </h2> <h3> Earn </h3> <p>We will issue a digital badge (and we will try to automate the process) when you earn a DevStream certification. Digital badges are active as long as your certification is valid.</p> <h3> Claim </h3> <p>You’ll get an email from Credly’s platform with instructions for claiming your badge. </p> <h3> Share </h3> <p>Use Credly’s platform sharing features to post on your social media newsfeeds and add your digital badge to your professional profile, as mentioned above.</p> devops go devstream community DevStream Joins CNCF Sandbox Tiexin Guo Thu, 16 Jun 2022 03:54:59 +0000 https://dev.to/devstream/devstream-joins-cncf-sandbox-4h36 https://dev.to/devstream/devstream-joins-cncf-sandbox-4h36 <p>On Jun 14, 2022, in the CNCF TOC Meeting, DevStream passed the technical oversight committee's review, and joined CNCF sandbox!</p> <h2> Background </h2> <p>DevStream dated back in Oct 2021 and released the first minor version v0.1.0 in Feb 2022. Now, DevStream is a big family with 26 contributors and hundreds of users in our user group. We've attracted some attention, too. Now, we are on our way to 400 GitHub stars, which is all organic growth!</p> <h2> Thank You </h2> <p>Here, I, Tiexin Guo, PMC Chair of DevStream, representing our whole team, would like to say a heartfelt "thank you" to the community.</p> <p>Joining the CNCF wouldn't be remotely possible if it weren't for you guys: all the users, contributors, and organization members.</p> <p>Here in the DevStream community, we often ask ourselves this question: what is DevStream? What is DevStream's core value? What defines DevStream? An open-source DevOps toolchain manager? Setting up your DevOps platform at the click of a button within 5 minutes?</p> <p>While those are all true, they <em>don't</em> define DevStream.</p> <blockquote> <p><em>You</em> define DevStream. You <em>are</em> DevStream. DevStream wouldn't be a thing without our passionate community.</p> </blockquote> <h2> The CNCF TOC Review </h2> <p>Oh, by the way, we didn't just pass the TOC review, or meet the bare minimum requirements. It's proud and fair to say that we passed with flying colors. </p> <p>Normally, the CNCF TOC meeting is held every two months, and they review a bunch of projects in that one-hour meeting. Some projects are scrutinized; it's not uncommon to see the TOC carefully review a project for about 10 minutes.</p> <p>We passed within 2 minutes and 10 seconds. Yep, that's not a typo :) See those glorious two minutes <a href="proxy.php?url=https://youtu.be/zcTZ2pYPM9I?t=2508">here on youtube</a>. Or, you can choose to watch the whole video if you would like to know more about how CNCF TOC meeting works:</p> <p><iframe width="710" height="399" src="proxy.php?url=https://www.youtube.com/embed/zcTZ2pYPM9I"> </iframe> </p> <p>The review of DevStream starts from <a href="proxy.php?url=https://youtu.be/zcTZ2pYPM9I?t=2508">41:48</a>.</p> <p>The result is officially documented <a href="proxy.php?url=https://lists.cncf.io/g/cncf-toc/topic/results_from_sandbox/91754161">here</a>; @amye has also <a href="proxy.php?url=https://github.com/cncf/toc/pull/860/files#diff-434180cbdead0d6d7a28f6e5961f360cd965ca1c75bd9b2cd736b9d318a59bb1R135">made a PR already to have DevStream listed</a>.</p> <h2> What We Did Right </h2> <p>This honor belongs to the community, and here I'd like to give back to the community a short list of actions that we think we did right from the beginning and accelerated everything:</p> <ul> <li>good documentation</li> <li>good roadmap</li> <li>some main contributors</li> <li>consistent contributions from the community</li> <li>community meetings</li> <li>a slack channel (where the contributors communicate asynchronously)</li> </ul> <p>In the doc, it's also critical to have some form of developer guide that lowers the bar and reduces the barriers for new contributors. After all, open-source is all about community.</p> <h2> Summary </h2> <p>In the next blog post, I will try to write a short FAQ which I summarized from CNCF TOC meetings. I will try my best to answer questions like:</p> <ul> <li>What exactly will the TOC meeting review?</li> <li>What aspects are important?</li> </ul> <p>Stay tuned. See you in the next one!</p> cncf opensource devops devstream A Brief Introduction to Code Review: Everything You Want to Know Tiexin Guo Mon, 06 Jun 2022 05:09:03 +0000 https://dev.to/devstream/a-brief-introduction-to-code-review-everything-you-want-to-know-1gea https://dev.to/devstream/a-brief-introduction-to-code-review-everything-you-want-to-know-1gea <p>This is <a href="proxy.php?url=https://www.guotiexin.com/">Tiexin Guo</a>, <a href="proxy.php?url=https://github.com/devstream-io/devstream">DevStream</a> <a href="proxy.php?url=https://github.com/orgs/devstream-io/teams/pmc">PMC</a> Chair (an open-source DevOps project with an enthusiastic community.)</p> <p>And today, we are going to talk about code review.</p> <p>Specifically, we are going to talk about:</p> <ul> <li>why do we need to do code reviews</li> <li>how to review;</li> <li>how to do code review on GitHub</li> <li>how to review for open-source projects</li> </ul> <p>But if you want to get acquainted with some best practices for reviewing code of any kind (closed-source projects, on GitLab, etc.,) please do read on, because the principles and methodologies are universal.</p> <p>Without further adieu, let's get started.</p> <h2> 1 Why Do We Need to Do Code Review? </h2> <p>I know you might already have a question in your head even before reading the title of this post: why do we need to do a code review?</p> <p>An excellent question, indeed. Because once you feed the code into a compiler, the result will always be the same. Why on earth do we need to bother reviewing code and writing clean code, anyway?</p> <p>Because code is for humans to read, not for machines to run.</p> <p>"Clean or not" makes no difference to a machine, but it makes all the difference for humankind. Cleaner code is much easier to organize, refactor, add new features into, debug, to ...</p> <p>Adding a new feature into a bad codebase could easily cost 100x the time compared to a clean codebase. If it's possible at all.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--4cv2tj6E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://martinfowler.com/articles/is-quality-worth-cost/poor.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--4cv2tj6E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://martinfowler.com/articles/is-quality-worth-cost/poor.png" alt="" width="857" height="257"></a></p> <p>So, the answer is easy: code review is for code quality.</p> <h2> 2 Why Not Using Tools and Automation for Code Quality? </h2> <p>There are already a bunch of tools or third-party services which can help you to evaluate the quality of a piece of code (don't ask me how I knew this.)</p> <p>For example, GitHub already showed how many lines of code changes there are in a PR. There are tools to tell you what is a "big" function, a "large" file; how many "big" functions and "large" files you've got in your repo; their percentage; etc.</p> <p>But hey, you can invent a million metrics, and they are still only "indicators" of code quality; they don't "define" good code.</p> <p>For example, in my previous company, we used an in-house tool that would check if the length of a function exceeds 50 lines of code. Why 50? why not 40, or 60? If it's 51, is it "clean"? If not, how about I delete a necessary empty line intended for separating two blocks? How about I delete a necessary comment?</p> <p>No metric is perfect, and there are a bunch of ways to hack it and bypass it.</p> <p>The only golden standard of code quality is for humans to review. Because code is meant to be read by humans.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--YkqHv9Zp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fk3fgj4jolyncqtxyy2p.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--YkqHv9Zp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fk3fgj4jolyncqtxyy2p.png" alt="Image description" width="880" height="504"></a></p> <h2> 3 Code Review: What Are the Standards? </h2> <p>Once we've figured out why we need to do it, we need to answer this not-so-technical question: what kind of code needs to be refactored? What kind of code needs to be rewritten? What can be merged? Anyway, what standard should I use?</p> <p>I know what you are thinking, and you are right: for code with obvious bugs and errors, you can't merge'em. This isn't in the scope of our discussion.</p> <p>How about code that runs properly, does what needs to be done, but has a few "defects," like "missing necessary comments," or "not easy to read/understand?"</p> <h3> 3.1 New Contributors </h3> <p>I'd like to make it clear: for new contributors, it's absolutely fine if they didn't meet your standard (in the beginning.)</p> <p>For new contributors, it's key to let them feel they are welcome, and there isn't much that hinders their contribution. Easy to understand: if it's extremely difficult to contribute, they won't. So, the thing is: don't let new contributors feel frustrated. We need to make the process as welcoming as possible for newcomers. Nothing is more important than letting new contributors feel easy and welcome.</p> <p>But, this isn't to say that we tolerate "dirty" code. If a new contributor made a commit that isn't perfect to your standard, guiding them and helping them to learn is the best way, and we should do it encouragingly. They will learn and next time they won't make the same mistakes again. We can even lead by examples, and give them sample code snippets, or even create another improvement commit and let them know.</p> <h3> 3.2 Senior Members </h3> <p>For senior contributors and members, we intend to keep the quality high.</p> <p>It's not enough if a PR merely does what it's supposed to. The readability should be fine, too. Evidently, a quick review reduces the time of code review and we could release a new feature quickly, but in the long run, careless reviews would give us a run for our money: as code quality decreases, it would take longer and longer to do even the most simple task.</p> <p>To sum up, we should:</p> <ul> <li>keep it simple and welcoming for new contributors;</li> <li>keep the bar high for ourselves, core developers, and community members.</li> </ul> <h2> 4 Working in an Open-Source Project </h2> <p>Last month, 3 more contributors joined the DevStream community. Circled (or to be precise, rectangulared) in red:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Ivam5cQN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ff7bzm2gpdo994crfuzf.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Ivam5cQN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ff7bzm2gpdo994crfuzf.png" alt="Image description" width="880" height="698"></a></p> <p>Now, we have seven active members in total. Plus, we've also got c.a. 20 external contributors. Yep, that's not a typo; you read it right: we've got more than 20 contributors, already.</p> <p>As a leader in an open-source project, it's not easy managing an open-source project, coding, reviewing all those pull requests (PR) from the community every day, AND still having a life (don't ask me how I knew this.)</p> <p>I guess this isn't uncommon for many project leads. Yes, you need to manage priorities properly and get things done, but there is a line when you simply have too many things to do and can't finish them all.</p> <p>Time to have a "reviewers" group and start "group" reviews! Simple answer, isn't it?</p> <p>But how?</p> <p>What's the process? What's the standard? Any tricks to speed it up while keeping the quality high in the air? A million questions to be answered.</p> <p>OK, let's try to tackle some of the important ones in this blog post.</p> <h2> 5 Taking up a Code Review Task </h2> <p>Let's do a code review, now.</p> <p>Before start reviewing anything, let's accept a quest first.</p> <p>Why do we need to do this? I'm glad you asked.</p> <p>The open-source community works asynchronously. That is to say, they don't do too much live communication, like phone calls, video meetings, or even offline, face-to-face meetings. Instead, they rely heavily on messages (by using instant message tools, but probably not reading them instantly) and emails. You need to let other people know that you are on this task so that others won't do the same thing. </p> <p>Even if you are not in an open-source project, for example, in a closed-source, internal project with a team sitting together around the table, it's also nice to let others know that "hey guys, I'll work on this, please don't do duplicated work."</p> <p>OK, then how to mark a task as yours?</p> <p>If you are already the reviewer of a PR, the "Reviewers" section will show your avatar, and there will be some hint in the yellow bar saying that "<em>This pull request is waiting on your review.</em>" There is also a green button "<em>Add your review</em>." Click this button to start a reviewing process.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--9XpDApLi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4b2o47rv145ufitg9kf.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--9XpDApLi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4b2o47rv145ufitg9kf.png" alt="Image description" width="880" height="579"></a></p> <p>Then how to become a "reviewer"?</p> <p>Easy question (and not easy at the same time:)</p> <p>Since you are a reviewer, you have permission to click the gear "⚙️" icon to assign a reviewer (for example, yourself.) But if you are not a legit reviewer yet, you should become a reviewer first.</p> <h2> 6 GitHub Code Review Walk-Through </h2> <p>Click the "<em>Add your review</em>" button and we will be directed to the Code Review page:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--9m3Br0a8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpqrtkr7n0h6kanzls9g.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--9m3Br0a8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpqrtkr7n0h6kanzls9g.png" alt="Image description" width="880" height="451"></a></p> <p>Here, we have a few features worth exploring:</p> <ul> <li>the "file tree" on the left</li> <li>file filter</li> <li>the "Viewed" checkbox for each file</li> <li>the "..." button</li> </ul> <p>Once you've gotten familiar with all of those quirks and features, your code review life quality will be greatly improved.</p> <p>To read more details about these, check out the <a href="proxy.php?url=https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/reviewing-proposed-changes-in-a-pull-request">official doc here</a>. Explore on. I'll wait for you to finish that doc first.</p> <p>For simple code changes, it's more than enough to view the code diff on the code review web page. Example:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--wFFp--Wz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5wq2m956oekhc5hbzvp7.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--wFFp--Wz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5wq2m956oekhc5hbzvp7.png" alt="Image description" width="880" height="528"></a></p> <p>We could easily tell if this change makes sense or not (hint: No. Missing comma. Try to find it :) If it's fixed, we can approve it:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--NGcoHZ01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l2wh1fzyeals2hn2cjm.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--NGcoHZ01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l2wh1fzyeals2hn2cjm.png" alt="Image description" width="880" height="587"></a></p> <h2> 7 Reviewing a PR Locally </h2> <p>For more complicated PRs, if the author also didn't include any screenshot of the test they had run (<a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/405">see this example here</a>,) it's better to check out the code locally and review it with the help of an <a href="proxy.php?url=https://en.wikipedia.org/wiki/Integrated_development_environment">IDE</a>.</p> <p>Take this PR for example: <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/641">pr #641</a>. To download this PR locally, let's execute:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>git fetch upstream pull/641/head:pr641 git checkout pr641 </code></pre> </div> <p>Results:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--OCnbD3Ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/698zag4oza65hs0qmxoq.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--OCnbD3Ie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/698zag4oza65hs0qmxoq.png" alt="Image description" width="880" height="490"></a></p> <p>Then we simply start our IDE and read the code there:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--vIDmhQjY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7uvskyjou5is33tuz2wb.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--vIDmhQjY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7uvskyjou5is33tuz2wb.png" alt="Image description" width="880" height="487"></a></p> <p>In IDE, it's much easier to spot possible mistakes (like missing a comma.) And, you can also build, test, and run it locally to see if it passes the unit tests and even integration tests.</p> <p>You might think this is redundant because a PR might have triggered a CI process already: </p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--whLtgMut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5llepuqk8p26kwcsgl0.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--whLtgMut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5llepuqk8p26kwcsgl0.png" alt="Image description" width="880" height="693"></a></p> <p>But hey, not all CI workflows are born equally. For example, not all CI workflows would run integration tests. So, testing a PR locally is still necessary for bigger PRs and for more complicated changes, to do a quick regression.</p> <p>So far, you can fully understand what a PR has changed.</p> <h2> 8 Committing to Somebody Else's PR </h2> <p>A general rule of thumb: don't.</p> <p>If you want to do it very badly, rethink your intention. Ask yourself: do you have to do this?</p> <p>Normally, it's not recommended to commit to other people's work directly. It's not respectful to the original author, and it's complicated (especially if the original author needs to add another commit.)</p> <p>But, rules are to be broken. Like every other single rule in the world, there are exceptions to this rule.</p> <p>Taking <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/589">this PR</a> for an example:</p> <p>It's from a new contributor. We don't want to make "contributing to the project" too hard, which would shut the door and scare too many potential contributors away.</p> <p>Plus, this PR isn't simple enough to get it 100% right. It seems easy, but to make it perfect, you'd need to know all <code>dtm</code> sub-commands, which certainly is a high bar to cross for a first-time contributor.</p> <p>That's why we added another commit to improve his work (important: and also let him know about it,) and included the test result:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--x4YvY48h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ys6p1000tignie0pyp6.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--x4YvY48h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ys6p1000tignie0pyp6.png" alt="Image description" width="880" height="440"></a></p> <p>How to do this exactly?</p> <p>First, make a local commit.</p> <p>Make changes, then commit:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>git add xxx git commit <span class="nt">-m</span> <span class="s2">"bug: yyy"</span> </code></pre> </div> <p>Then, we need to find the PR's source branch.</p> <p>We can find out from which branch this PR comes from:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--TSTr_YtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ya7y5ft6kx02itdgh2y.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--TSTr_YtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ya7y5ft6kx02itdgh2y.png" alt="Image description" width="880" height="439"></a></p> <p>Now we see the "fork" info, like:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--1QYowSjM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dolek4utu2fub3fvuhld.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--1QYowSjM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dolek4utu2fub3fvuhld.png" alt="Image description" width="880" height="331"></a></p> <p>Then we can add a new remote:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>git remote add himku [email protected]:himku/devstream.git </code></pre> </div> <ul> <li>remote: <code>himku</code>(<a href="proxy.php?url=mailto:[email protected]">[email protected]</a>:himku/devstream.git)</li> <li>branch: <code>fix-issue-559</code> </li> </ul> <p>Then we push to it:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>git push himku HEAD:fix-issue-559 </code></pre> </div> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--8dJfM_z2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0mp00vxclt88ysg85tj.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--8dJfM_z2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0mp00vxclt88ysg85tj.png" alt="Image description" width="880" height="422"></a></p> <p>Cool, right? But again: rethink before you want to do this; because more often than not, you don't necessarily want to do it.</p> <h2> 9 Summary </h2> <p>Read more if you are interested:</p> <ul> <li>Google's <a href="proxy.php?url=https://google.github.io/eng-practices/review/">Code Review Developer Guide</a> </li> </ul> <p>If you like this article, please bookmark our DevStream blog and my personal blog. We are also on Medium and Dev.to. Here are the links for you:</p> <ul> <li>DevStream blog: <a href="proxy.php?url=https://blog.devstream.io">https://blog.devstream.io</a> </li> <li>Tiexin's blog: <a href="proxy.php?url=https://www.guotiexin.com">https://www.guotiexin.com</a> </li> <li>DevStream Medium: <a href="proxy.php?url=https://medium.com/devstream">https://medium.com/devstream</a> </li> <li>Tiexin's Medium: <a href="proxy.php?url=https://medium.com/@IronCore864">https://medium.com/@IronCore864</a> </li> <li>DevStream Dev.to: <a href="proxy.php?url=https://dev.to/devstream">https://dev.to/devstream</a> </li> <li>Tiexin's Dev.to: <a href="proxy.php?url=https://dev.to/ironcore864/">https://dev.to/ironcore864/</a> </li> </ul> <p>See you in the next article.</p> codereview codequality devstream github Using AWS S3 to Store DevStream State Tiexin Guo Mon, 30 May 2022 09:38:25 +0000 https://dev.to/devstream/using-aws-s3-to-store-devstream-state-1dgj https://dev.to/devstream/using-aws-s3-to-store-devstream-state-1dgj <p>In our <a href="proxy.php?url=https://blog.devstream.io/posts/v060-release/">latest release v0.6.0</a>, using AWS S3 to store DevStream state is supported.</p> <p>In this blog, we are going to demonstrate the usage of AWS S3 to store DevStream's state files.</p> <h2> Terminology </h2> <p><em>State: if you don't already know about DevStream state, please read <a href="proxy.php?url=https://docs.devstream.io/en/latest/core-concepts/core-concepts/">this doc</a> and <a href="proxy.php?url=https://blog.devstream.io/posts/creating-a-dtm-plugin-for-anything/">this blog</a> helps, too.</em></p> <p>Backend: where to actually store the state file. It can be either <code>local</code> or <code>s3</code> at the moment.</p> <h2> Configuration </h2> <p>In the main config file, we have a section to configure the state. Currently, local and S3 are supported.</p> <p>Local example:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">varFile</span><span class="pi">:</span> <span class="s">variables-gitops.yaml</span> <span class="na">toolFile</span><span class="pi">:</span> <span class="s">tools-gitops.yaml</span> <span class="na">state</span><span class="pi">:</span> <span class="na">backend</span><span class="pi">:</span> <span class="s">local</span> <span class="na">options</span><span class="pi">:</span> <span class="na">stateFile</span><span class="pi">:</span> <span class="s">devstream.state</span> </code></pre> </div> <p>S3 example:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">varFile</span><span class="pi">:</span> <span class="s">variables-gitops.yaml</span> <span class="na">toolFile</span><span class="pi">:</span> <span class="s">tools-gitops.yaml</span> <span class="na">state</span><span class="pi">:</span> <span class="na">backend</span><span class="pi">:</span> <span class="s">s3</span> <span class="na">options</span><span class="pi">:</span> <span class="na">bucket</span><span class="pi">:</span> <span class="s">devstream-remote-state</span> <span class="na">region</span><span class="pi">:</span> <span class="s">ap-southeast-1</span> <span class="na">key</span><span class="pi">:</span> <span class="s">devstream.state</span> </code></pre> </div> <p>More on configuring state <a href="proxy.php?url=https://docs.devstream.io/en/latest/core-concepts/stateconfig/">here</a>.</p> <p>In short, we can use the "backend" keyword to specify where to store the state: either locally or in an S3 bucket. If S3 is used, we need to specify the bucket, region, and the S3 key as well.</p> <h2> Config File Examples </h2> <p>In this demo, we use the following configs:</p> <p><code>config.yaml</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">varFile</span><span class="pi">:</span> <span class="s">variables-gitops.yaml</span> <span class="na">toolFile</span><span class="pi">:</span> <span class="s">tools-gitops.yaml</span> <span class="na">state</span><span class="pi">:</span> <span class="na">backend</span><span class="pi">:</span> <span class="s">s3</span> <span class="na">options</span><span class="pi">:</span> <span class="na">bucket</span><span class="pi">:</span> <span class="s">devstream-test-remote-state</span> <span class="na">region</span><span class="pi">:</span> <span class="s">ap-southeast-1</span> <span class="na">key</span><span class="pi">:</span> <span class="s">devstream.state</span> </code></pre> </div> <p><code>variables-gitops.yaml</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">githubUsername</span><span class="pi">:</span> <span class="s">IronCore864</span> <span class="na">repoName</span><span class="pi">:</span> <span class="s">dtm-test-go</span> <span class="na">defaultBranch</span><span class="pi">:</span> <span class="s">main</span> <span class="na">dockerhubUsername</span><span class="pi">:</span> <span class="s">ironcore864</span> <span class="na">argocdNameSpace</span><span class="pi">:</span> <span class="s">argocd</span> <span class="na">argocdDeployTimeout</span><span class="pi">:</span> <span class="s">5m</span> </code></pre> </div> <p><code>tools-gitops.yaml</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">tools</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">github-repo-scaffolding-golang</span> <span class="na">instanceID</span><span class="pi">:</span> <span class="s">default</span> <span class="na">options</span><span class="pi">:</span> <span class="na">owner</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">githubUsername</span> <span class="pi">]]</span> <span class="na">org</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span> <span class="na">repo</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">repoName</span> <span class="pi">]]</span> <span class="na">branch</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">defaultBranch</span> <span class="pi">]]</span> <span class="na">image_repo</span><span class="pi">:</span> <span class="pi">[[</span> <span class="nv">dockerhubUsername</span> <span class="pi">]]</span><span class="s">/[[ repoName ]]</span> </code></pre> </div> <h2> Getting Started </h2> <p>Before reading on, now is a good time to check if you have configured your AWS related environment variables correctly or not.</p> <p>For macOS/Linux users, do:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nb">export </span><span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>ID_HERE <span class="nb">export </span><span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span>SECRET_HERE <span class="nb">export </span><span class="nv">AWS_DEFAULT_REGION</span><span class="o">=</span>REGION_HERE </code></pre> </div> <p>For more information, see the <a href="proxy.php?url=https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html">official document here</a>.</p> <h2> Apply </h2> <p>Then let's run <code>dtm apply</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>tiexin@mbp ~/work/devstream-io/devstream <span class="nv">$ </span>./dtm apply 2022-05-30 17:07:59 ℹ <span class="o">[</span>INFO] Apply started. 2022-05-30 17:07:59 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-05-30 17:07:59 ℹ <span class="o">[</span>INFO] Using s3 backend. Bucket: devstream-test-remote-state, region: ap-southeast-1, key: devstream.state. 2022-05-30 17:08:00 ℹ <span class="o">[</span>INFO] Tool <span class="o">(</span>github-repo-scaffolding-golang/default<span class="o">)</span> found <span class="k">in </span>config but doesn<span class="s1">'t exist in the state, will be created. Continue? [y/n] Enter a value (Default is n): y 2022-05-30 17:08:08 ℹ [INFO] Start executing the plan. 2022-05-30 17:08:08 ℹ [INFO] Changes count: 1. 2022-05-30 17:08:08 ℹ [INFO] -------------------- [ Processing progress: 1/1. ] -------------------- 2022-05-30 17:08:08 ℹ [INFO] Processing: (github-repo-scaffolding-golang/default) -&gt; Create ... 2022-05-30 17:08:12 ℹ [INFO] The repo dtm-test-go has been created. 2022-05-30 17:08:29 ✔ [SUCCESS] Tool (github-repo-scaffolding-golang/default) Create done. 2022-05-30 17:08:29 ℹ [INFO] -------------------- [ Processing done. ] -------------------- 2022-05-30 17:08:29 ✔ [SUCCESS] All plugins applied successfully. 2022-05-30 17:08:29 ✔ [SUCCESS] Apply finished. </span></code></pre> </div> <p>As we can see from the output, the S3 backend is used, and it also shows the bucket and key you are using, and in which region this bucket lives.</p> <h2> Checking the State File </h2> <p>After <code>apply</code>, let's download the state file from S3 and check it out:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>tiexin@mbp ~/work/devstream-io/devstream <span class="nv">$ </span>aws s3 <span class="nb">cp </span>s3://devstream-test-remote-state/devstream.state <span class="nb">.</span> </code></pre> </div> <p>And if we open the downloaded file, we will see something similar to the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">github-repo-scaffolding-golang_default</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">github-repo-scaffolding-golang</span> <span class="na">instanceid</span><span class="pi">:</span> <span class="s">default</span> <span class="na">dependson</span><span class="pi">:</span> <span class="pi">[]</span> <span class="na">options</span><span class="pi">:</span> <span class="na">branch</span><span class="pi">:</span> <span class="s">main</span> <span class="na">image_repo</span><span class="pi">:</span> <span class="s">ironcore864/dtm-test-go</span> <span class="na">org</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span> <span class="na">owner</span><span class="pi">:</span> <span class="s">IronCore864</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">dtm-test-go</span> <span class="na">resource</span><span class="pi">:</span> <span class="na">org</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span> <span class="na">outputs</span><span class="pi">:</span> <span class="na">org</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span> <span class="na">owner</span><span class="pi">:</span> <span class="s">IronCore864</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">dtm-test-go</span> <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/IronCore864/dtm-test-go.git</span> <span class="na">owner</span><span class="pi">:</span> <span class="s">IronCore864</span> <span class="na">repoName</span><span class="pi">:</span> <span class="s">dtm-test-go</span> </code></pre> </div> <p>which is exactly the same as if we were using the local backend to store state.</p> <p>Like this quick tutorial? Then I suggest to read more of our latest DevOps blogs:</p> <ul> <li><a href="proxy.php?url=https://blog.devstream.io/posts/9-terraform-best-practices/">9 Extraordinary Terraform Best Practices That Will Change Your Infra World</a></li> <li><a href="proxy.php?url=https://blog.devstream.io/posts/dagger-in-depth/">Dagger (the CI/CD Tool, not the Knife) In-Depth - Everything You Need to Know (as of Apr 2022)</a></li> <li><a href="proxy.php?url=https://blog.devstream.io/posts/dmca-takedowns/">A Brief History of the DMCA</a></li> </ul> devops devstream aws s3 DevStream v0.6.0 Release Tiexin Guo Wed, 25 May 2022 07:47:33 +0000 https://dev.to/devstream/devstream-v060-release-3pgj https://dev.to/devstream/devstream-v060-release-3pgj <h2> Demo </h2> <p>Talk is cheap. Show me the demo:</p> <p><a href="proxy.php?url=https://www.youtube.com/watch?v=q7TK3vFr1kg"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--bmXwxPEY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.ytimg.com/vi/q7TK3vFr1kg/hqdefault.jpg" alt="DevStream v0.6.0 Release" width="480" height="360"></a></p> <h2> Official Releases for Different Platforms </h2> <ul> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.6.0/dtm-darwin-arm64">dtm-darwin-arm64</a></li> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.6.0/dtm-darwin-amd64">dtm-darwin-amd64</a></li> <li><a href="proxy.php?url=https://devstream.gateway.scarf.sh/releases/v0.6.0/dtm-linux-amd64">dtm-linux-amd64</a></li> </ul> <h2> Major Changes </h2> <ul> <li>Plugins now are released to an AWS S3 bucket. <code>dtm init</code> also downloads plugins from S3.</li> <li>AWS S3 can be used to store DevStream state.</li> </ul> <h2> New Contributors </h2> <ul> <li> <a class="mentioned-user" href="proxy.php?url=https://dev.to/aeinrw">@aeinrw</a> made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/456">https://github.com/devstream-io/devstream/pull/456</a> </li> <li>@lyleshaw made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/463">https://github.com/devstream-io/devstream/pull/463</a> </li> <li>@aFlyBird0 made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/479">https://github.com/devstream-io/devstream/pull/479</a> </li> <li>@wzymumon made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/477">https://github.com/devstream-io/devstream/pull/477</a> </li> <li>@iyear made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/498">https://github.com/devstream-io/devstream/pull/498</a> </li> <li>@SongDunYu made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/540">https://github.com/devstream-io/devstream/pull/540</a> </li> <li>@shubham-cmyk made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/536">https://github.com/devstream-io/devstream/pull/536</a> </li> <li>@yclchuxue made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/553">https://github.com/devstream-io/devstream/pull/553</a> </li> <li>@24sama made their first contribution in <a href="proxy.php?url=https://github.com/devstream-io/devstream/pull/562">https://github.com/devstream-io/devstream/pull/562</a> </li> </ul> <p>Congratulations, and keep up the good work!</p> <h2> More </h2> <p><strong>Full Changelog</strong>: <a href="proxy.php?url=https://github.com/devstream-io/devstream/compare/v0.5.0...v0.6.0">https://github.com/devstream-io/devstream/compare/v0.5.0...v0.6.0</a></p> <p>For more details, see the <a href="proxy.php?url=https://github.com/devstream-io/devstream/releases/tag/v0.6.0">GitHub release page here</a>.</p> devstream devops devsecops tooling On DevOps: 1. What It Is Tiexin Guo Thu, 12 May 2022 02:23:28 +0000 https://dev.to/devstream/on-devops-1-what-it-is-3lmd https://dev.to/devstream/on-devops-1-what-it-is-3lmd <blockquote> <p>Note: opinions are my own. They don't represent my current company or any previous companies I've worked for.</p> <p>Note 2: recently, my colleague published a blog post (in Chinese, <a href="proxy.php?url=https://blog.devstream.io/posts/%E5%BD%93%E6%88%91%E4%BB%AC%E5%9C%A8%E8%AF%B4devops-sre%E6%97%B6-%E6%88%91%E4%BB%AC%E5%9C%A8%E8%AF%B4%E4%BB%80%E4%B9%88/">here's the link</a> if you are interested). I like this post, but I'd like to share more with you on the topic of DevOps. So I decided to publish a miniseries (7 episodes planned) on DevOps. Here's the first article.</p> <p>Note 3: this article is originally published on Jan 14, 2021, on Medium in <a href="proxy.php?url=https://medium.com/4th-coffee/">my DevOps Chat publication named 4th Coffee</a>. See it <a href="proxy.php?url=https://medium.com/4th-coffee/on-devops-1-what-it-is-549bc4e6b1ed">here</a> if you are interested. This post is edited again on May 10th, 2022.</p> </blockquote> <h2> Background </h2> <p>I’ve been working as a DevOps engineer since 2016, and as of today in 2022, I'm still working on it (as the PMC of <a href="proxy.php?url=https://github.com/devstream-io/devstream">DevStream</a>). In 2021, I was lucky enough to join AWS as a Senior (L6) DevOps (already left, though). But this article doesn’t represent AWS’s view; nor does it represent my current corporate view. It’s my personal opinion, which I learned and formulated over the years of hands-on experience in projects.</p> <p>In the beginning of 2021, I had the chance to talk on the DevOps topic — what it is, why we need it, and how to measure if we are doing it well or not. So I thought I should take that opportunity, form my thoughts into words, and share them with the community.</p> <p>In my professional career, I’ve worked in different businesses, including university research, search engine, government, internet, enterprise software, mobile, marketing business intelligence, logistics, automotive, pharmaceutical, insurance, consulting, etc.; and I’ve worked in different types of companies and projects: state-owned company, local internet giant, global IT enterprise, foreign start-ups, global traditional organizations, etc. And I have to say that I have learned and grown a lot during this great journey both as a person and as an engineer (full stack developer and DevOps engineer). I also attended a few conferences and meet-ups and even gave some talks in them, but I have never got the chance to summarize what I do and share the knowledge with a bigger audience, and that’s the reason I wanted to start this series.</p> <h2> What is DevOps? </h2> <p>Let Me Tell You a Story First.</p> <p>I have interviewed over 200 engineers in the past 3 years, and sometimes I’d like to ask this question. It’s probably meaningless; I only asked it because I want to see what it means to everybody.</p> <p>And in all the answers I got, it seems most people always start with this sentence: “that’s a good question.” Then the person starts to explain his thoughts.</p> <p>I’m not a native English speaker, but over the years, I’ve been gradually learning the fact that “that’s a good question” equals “I don’t know.”</p> <p>It’s a meaningless filler phrase to fill the awkward silence during conversations, and it buys you some time to organize your thought before you answer the question.</p> <p>Only on infrequent occasions would you hear this sentence because you really had asked an excellent question.</p> <p>I have also interviewed (as an interviewee) many companies over the last 5 years, both start-ups and large corporations. I also got asked about this quite often, at least more often than I had anticipated. Sometimes, during the conversation, you started to realize that the person or the company who asked this question didn’t really know the answer either; they asked this because they wanted to learn from you and see what new ideas you can bring to the team.</p> <h2> Why Is It So Hard to Define DevOps? </h2> <p>Why is this question hard to answer?</p> <p>Because, in my opinion, DevOps is a lot.</p> <h2> Let’s Try to Figure It out </h2> <p>Let me quote some definitions first before we discuss DevOps.</p> <p>Here I quote three definitions, and they are from Wikipedia (because it’s like an internet dictionary), AWS (because we all hear that AWS does good DevOps), and Atlassian (because as a DevOps, we probably use their tools a lot so they might know a thing or two):</p> <p>Wikipedia:</p> <blockquote> <p>DevOps is a set of practices that combines <a href="proxy.php?url=https://en.wikipedia.org/wiki/Software_development">software development</a> (Dev) and <a href="proxy.php?url=https://en.wikipedia.org/wiki/IT_operations">IT operations</a> (Ops). It aims to shorten the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Systems_development_life_cycle">systems development life cycle</a> and provide <a href="proxy.php?url=https://en.wikipedia.org/wiki/Continuous_delivery">continuous delivery</a> with high <a href="proxy.php?url=https://en.wikipedia.org/wiki/Software_quality">software quality</a>.<a href="proxy.php?url=https://en.wikipedia.org/wiki/DevOps#cite_note-Mala_2019_p._16-1">[1]</a><a href="proxy.php?url=https://en.wikipedia.org/wiki/DevOps#cite_note-loukides-2012-2">[2]</a> DevOps is complementary with <a href="proxy.php?url=https://en.wikipedia.org/wiki/Agile_software_development">agile software development</a>; several DevOps aspects came from Agile methodology.</p> </blockquote> <p>(I know, there are a lot of links. You don't have to read all of them, but it's recommended.)</p> <p>AWS:</p> <blockquote> <p>DevOps is the combination of cultural philosophies, practices, and tools that increases an organization’s ability to deliver applications and services at high velocity: evolving and improving products at a faster pace than organizations using traditional software development and infrastructure management processes. This speed enables organizations to better serve their customers and compete more effectively in the market.</p> </blockquote> <p>Atlassian:</p> <blockquote> <p>DevOps is a set of practices that works to automate and integrate the processes between software development and IT teams, so they can build, test, and release software faster and more reliably.</p> </blockquote> <p>It seems there are some similarities, which are:</p> <ul> <li><em>practices</em></li> <li><em>velocity</em></li> </ul> <p>And there are differences, of course, and let’s see them one by one:</p> <h2> A Combination of Dev and Ops </h2> <p>Wikipedia says it’s a combination of Dev and Ops.</p> <p>This basically says nothing because even if I knew nothing about this subject, I had already known from the word itself that it combines Dev and Ops.</p> <p>An excellent question here is: is DevOps really Dev + Ops?</p> <p>Yes, and No.</p> <p>Yes, because you would need the skillsets from both Dev and Ops.</p> <p>No, because simply putting two teams together wouldn’t get you there.</p> <h2> Cultural Philosophies </h2> <p>What AWS’s definition has and others don’t have, is the “cultural philosophies,” and I like it.</p> <p>Let’s say you have the tools and skillsets from both Dev and Ops. Would that be enough to enable you to achieve higher velocity?</p> <p>Probably not.</p> <p>The fact is, you already have Dev and Ops teams and the corresponding skillsets in your org. Then you pick up a new CI tool that is famous in the DevOps world. Would you be able to deliver faster already?</p> <p>I don’t think so.</p> <p>This is because how we do things is more important than what we have, and by definition, “how we do things” is the cultural part.</p> <h2> Culture </h2> <p>Culture is how human societies behave. What the customs are, what the norms are.</p> <p>For example:</p> <ul> <li>What is right, what is wrong?</li> <li>What is acceptable, and what isn’t?</li> <li>How do you learn?</li> <li>And more importantly, how do you encourage the team to learn?</li> </ul> <p>Culture can be highly technical. For example: how do you do code review? What types of code are acceptable, and what are not?</p> <p>And it can also be highly nontechnical. For example: what standards do we use for hiring and promoting?</p> <h2> DevOps Culture </h2> <p>Then, what exactly is the DevOps culture?</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--4uDsmWDg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.devstream.io/posts/on-devops-1-what-it-is/devops.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--4uDsmWDg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.devstream.io/posts/on-devops-1-what-it-is/devops.png" alt="devops ring" width="880" height="453"></a></p> <p>You probably see this image (or something very similar to this) a lot. And to be honest, the DevOps culture is hidden in this picture:</p> <p>You don’t separate Dev and Ops. You have a shared understanding and shared responsibility. You take up the responsibility as a whole through collaboration and communication. And this is the cornerstone of the DevOps culture.</p> <h2> Is DevOps a role? </h2> <p>With the culture part explained, it’s easy to answer this question.</p> <p>Yes, but no.</p> <p>Yes, because companies create job titles like this.</p> <p>No, because companies that created the job titles like this only put Ops in the DevOps team in many cases.</p> <p>It’s not really a job role. It’s more of a culture, methodologies, and mindset to do things. Putting teams together and throwing in some new tools won’t give you DevOps.</p> <h2> What is Required to Achieve the DevOps Culture? </h2> <p>Because we have shared understanding and shared responsibilities, traditional Dev needs to learn from Ops, and traditional Ops need to learn from Dev.</p> <p>Because we have new practices and tools, everybody needs constant learning about methodologies and new technologies.</p> <p>So, how to achieve a DevOps culture? At this point, this question can answer itself:</p> <p>DevOps requires a “growth and continuous learning mindset” to succeed.</p> <h2> How to Enable the Growth and Learning Mindset </h2> <p>Gartner, the globally leading research and advisory company, predicted in 2019 that by 2022, 75% of DevOps initiatives would fail to meet their expectations; this number grows to 90% by 2023.</p> <p>Reason: limitations of management approaches used by leadership</p> <p>Leadership support is one of the critical elements of its success.</p> <h2> Motivation </h2> <p>Let me tell you another story that you probably will relate to:</p> <p>I’ve worked in many start-up companies, and it seems many of those start-ups share a “start-up culture”: free breakfast, and free beer in the fridge.</p> <p>The idea behind this is simple: by creating a more welcoming environment, they think employees will be more motivated.</p> <p>Do I love free beer? Of course.</p> <p>Will this really motivate me? No.</p> <p>When I search for my next job position, I will not use “free beer in the fridge” as a standard. People who get paid at least 10k per month probably wouldn’t care if they can get 2 dollars worth of beer.</p> <p>Having a free beer in the fridge won’t motivate your team to learn and grow continuously.</p> <h2> What Motivates the Team to Grow </h2> <p>Technical people need 3 things to be motivated, and they are:</p> <ul> <li>Mastery</li> <li>Autonomy</li> <li>Purpose</li> </ul> <p>The reasons are easy to understand:</p> <p>When you have comprehensive knowledge or skill in a subject and have great accomplishments, you will be more motivated. It’s like that if you get more praise for your work in the school, you will be more determined to do it better next time.</p> <p>The team learns fast and wants to do greater things. If they are constantly blocked and slowed down, especially by leadership, they lose their motivation. It’s like driving a Porsche sportscar at 50km/h. You have the best tool, but you can’t speed up. You wouldn’t enjoy driving like that, and you lose interest in driving over time.</p> <p>The team needs to know why they are doing something. Because without a purpose, everything is just garbage.</p> <h2> Leadership </h2> <p>Once we figured out the culture, and what motivates the team to implement the culture, it’s easy to figure out what we should seek in leadership: we need necessary behavioral characteristics that empower and encourage the team, rather than technical skillsets and expertise, for example, if the leader is a professional in the cloud or 12-factor app or what have you.</p> <p>Leadership should encourage the team to learn fast, try fast, and fail fast because by embracing failure as a learning opportunity, you learn the fastest way.</p> <p>Leadership should empower the team, and give the team enough autonomy to make decisions free of second-guessing. I’ve seen exactly the opposite cases very often, and the leadership doesn't even know they are discouraging the team.</p> <p>Leadership should provide clear goals and key metrics as part of the motivation for the team’s continuous learning and growth mindset.</p> <h2> IT Enables Business </h2> <p>If you go back to the beginning where I quoted AWS’s definition of DevOps, you will see that AWS mentions something that neither Wikipedia nor Atlassian mentioned:</p> <blockquote> <p>This speed enables organizations to better serve their customers and compete more effectively in the market.</p> </blockquote> <p>Yes, all definitions mentioned DevOps' benefit being velocity, but only AWS explained why you need this velocity: to serve your customer.</p> <p>I like it because, without this purpose, DevOps is just an empty gesture.</p> <p>In modern days, more and more organizations start to realize that IT architecture not only serves the business because it’s a must-have but also enables more possibilities for new businesses and growth. And that’s where the DevOps’s benefit will bring you to.</p> <h2> Summary </h2> <p>After this long discussion, it’s safe to try to make a definition of DevOps:</p> <p>DevOps is a combination of:</p> <ul> <li>a shared responsibility model</li> <li>continuous growth and learning mindset</li> <li>skillsets from both Dev and Ops</li> <li>best practices</li> <li>modern toolchains</li> </ul> <p>that increases velocity to deliver faster, get customer feedback faster, and adapt your product and service faster to serve your customers better.</p> <p>In the next article or a few articles, I will discuss the benefits that DevOps brings.</p> devops devsecops aws productivity Getting Started with minikube Daniel Hu Sat, 23 Apr 2022 15:22:39 +0000 https://dev.to/devstream/getting-started-with-minikube-5c61 https://dev.to/devstream/getting-started-with-minikube-5c61 <p>What’s up guys, this is Daniel! Yeah, this English blog is WRITTEN by me, not TRANSLATED by me! It’s my first English blog in my whole life. If you are a Chinese reader, maybe you’ve ever seen my Chinese blogs before. Right, I wrote a lot of blogs, but only in Chinese. Believe it or not, English isn’t my strong suit. But I want to have a try today and I’ll do my best. Let’s get started.</p> <h2> What We Will Talk about Today </h2> <p>If you ask me how to create a local Kubernetes cluster, I'll tell you <a href="proxy.php?url=https://kind.sigs.k8s.io">Kind</a>, <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/">minikube</a>, and <a href="proxy.php?url=https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/">Kubeadm</a> are all good choices. If you ask me which tool is the most recommended for beginners, my answer must be <code>minikube</code> or <code>Kind</code>. <code>minikube</code> is more powerful than <code>Kind</code>, and <code>Kind</code> is easier than <code>minikube</code>.</p> <p>In my previous article, I walked you guys through "How to Create a Local Kubernetes Cluster from the Ground Up" with <code>Kind</code>. If you are interested, here's a quick link for you:</p> <ul> <li><a href="proxy.php?url=https://www.devstream.io/blog/local-k8s-with-kind#install-docker">Creating a Local Kubernetes Cluster from the Ground Up</a></li> </ul> <p>You might ask why that article is written by <a href="proxy.php?url=https://github.com/IronCore864">Tiexin Guo</a>, not me. Of course! As I said in the beginning, this blog you are reading now is my first English blog. So, technically, the one above isn’t “my” article, but I did write the Chinese version of it:</p> <ul> <li><a href="proxy.php?url=https://www.devstream.io/zh/blog/local-k8s-with-kind">从零开始快速搭建本地 Kubernetes 测试环节</a></li> </ul> <p>Both <code>minikube</code> and <code>Kind</code> are the most well-known and popular choices to run a Kubernetes environment on a local computer. Since I've shown you how to use <code>Kind</code>, I might as well introduce <code>minikube</code> to youadorable today.</p> <h2> Quick Start with <code>minikube</code> </h2> <p><code>minikube</code> is a command-line tool like <code>Kind</code> that you can use to create and configure Kubernetes clusters locally on your computer. </p> <h3> How to Install <code>minikube</code> </h3> <p>If your computer is the same as mine, Mac with m1 chip, you can install the latest <code>minikube</code> stable release in 2 steps:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>curl <span class="nt">-LO</span> https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64 <span class="nb">sudo install </span>minikube-darwin-arm64 /usr/local/bin/minikube </code></pre> </div> <p>If you are using <code>Homebrew</code>, just one command:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>brew <span class="nb">install </span>minikube </code></pre> </div> <p>If your computer is of other platforms or architectures, see the “Installation” section in <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/start/">minikube start</a> for more details.</p> <h3> <code>minikube start</code> </h3> <p>Like the section title we used here, to start using minikube, run <code>minikube start</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube start 😄 minikube v1.25.2 on Darwin 12.2.1 <span class="o">(</span>arm64<span class="o">)</span> 👎 Unable to pick a default driver. Here is what was considered, <span class="k">in </span>preference order: ▪ docker: Not healthy: <span class="s2">"docker version --format {{.Server.Os}}-{{.Server.Version}}"</span> <span class="nb">exit </span>status 1: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ▪ docker: Suggestion: Start the Docker service &lt;https://minikube.sigs.k8s.io/docs/drivers/docker/&gt; 💡 Alternatively you could <span class="nb">install </span>one of these drivers: ▪ hyperkit: Not installed: <span class="nb">exec</span>: <span class="s2">"hyperkit"</span>: executable file not found <span class="k">in</span> <span class="nv">$PATH</span> ▪ parallels: Not installed: <span class="nb">exec</span>: <span class="s2">"prlctl"</span>: executable file not found <span class="k">in</span> <span class="nv">$PATH</span> ▪ vmware: Not installed: <span class="nb">exec</span>: <span class="s2">"docker-machine-driver-vmware"</span>: executable file not found <span class="k">in</span> <span class="nv">$PATH</span> ▪ virtualbox: Not installed: unable to find VBoxManage <span class="k">in</span> <span class="nv">$PATH</span> ▪ podman: Not installed: <span class="nb">exec</span>: <span class="s2">"podman"</span>: executable file not found <span class="k">in</span> <span class="nv">$PATH</span> ❌ Exiting due to DRV_DOCKER_NOT_RUNNING: Found docker, but the docker service isn<span class="s1">'t running. Try restarting the docker service. </span></code></pre> </div> <p>Ok, it doesn’t matter; we need to install Docker. In my previous article <a href="proxy.php?url=https://www.devstream.io/blog/local-k8s-with-kind#install-docker">How to Create a Local Kubernetes Cluster from the Ground Up</a>, I introduced how to "Install Docker".</p> <p>After the Docker is installed, what we'll need in total?</p> <ul> <li>A not too bad computer;</li> <li>Internet connections;</li> <li>Container or virtual machine hypervisor, such as: Docker, Hyper-V, KVM, VirtualBox, or VMware Workstation ...</li> </ul> <p>Let's try to run <code>minikube start</code> again. This command is time-consuming the first time you use it, especially if the network speed is low. Feel free to have a cup of coffee then come back to see the result.</p> <p>2000 years later...</p> <p>Well Done! 0 error, 0 warning! The logs are beautiful!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube start 😄 minikube v1.25.2 on Darwin 12.2.1 <span class="o">(</span>arm64<span class="o">)</span> ✨ Automatically selected the docker driver 👍 Starting control plane node minikube <span class="k">in </span>cluster minikube 🚜 Pulling base image ... 💾 Downloading Kubernetes v1.23.3 preload ... <span class="o">&gt;</span> preloaded-images-k8s-v17-v1...: 419.07 MiB / 419.07 MiB 100.00% 8.54 MiB <span class="o">&gt;</span> index.docker.io/kicbase/sta...: 28.23 MiB / 343.12 MiB 8.23% 1.34 MiB p/ <span class="o">&gt;</span> index.docker.io/kicbase/sta...: 343.12 MiB / 343.12 MiB 100.00% 3.77 MiB ❗ minikube was unable to download gcr.io/k8s-minikube/kicbase:v0.0.30, but successfully downloaded docker.io/kicbase/stable:v0.0.30 as a fallback image 🔥 Creating docker container <span class="o">(</span><span class="nv">CPUs</span><span class="o">=</span>2, <span class="nv">Memory</span><span class="o">=</span>3885MB<span class="o">)</span> ... 🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ... ▪ kubelet.housekeeping-interval<span class="o">=</span>5m ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner, default-storageclass ❗ /usr/local/bin/kubectl is version 1.21.5, which may have incompatibilites with Kubernetes 1.23.3. ▪ Want kubectl v1.23.3? Try <span class="s1">'minikube kubectl -- get pods -A'</span> 🏄 Done! kubectl is now configured to use <span class="s2">"minikube"</span> cluster and <span class="s2">"default"</span> namespace by default </code></pre> </div> <h3> Access Your Shiny Kubernetes Cluster </h3> <p>Do you have <code>kubectl</code> installed? If yes, you can now use it to access your shiny Kubernetes cluster:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl get pod <span class="nt">-A</span> NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-64897985d-hdmqd 1/1 Running 0 3m10s kube-system etcd-minikube 1/1 Running 0 3m24s kube-system kube-apiserver-minikube 1/1 Running 0 3m25s kube-system kube-controller-manager-minikube 1/1 Running 0 3m25s kube-system kube-proxy-nkzmn 1/1 Running 0 3m10s kube-system kube-scheduler-minikube 1/1 Running 0 3m23s kube-system storage-provisioner 1/1 Running 1 <span class="o">(</span>2m40s ago<span class="o">)</span> 3m22s </code></pre> </div> <p>Alternatively, <code>minikube</code> can download the appropriate version of <code>kubectl</code> with the command <code>minikube kubectl</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>minikube kubectl <span class="nt">--</span> get pod <span class="nt">-A</span> <span class="o">&gt;</span> kubectl.sha256: 64 B / 64 B <span class="o">[</span><span class="nt">--------------------------</span><span class="o">]</span> 100.00% ? p/s 0s <span class="o">&gt;</span> kubectl: 52.82 MiB / 52.82 MiB <span class="o">[</span><span class="nt">--------------</span><span class="o">]</span> 100.00% 6.04 MiB p/s 8.9s NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-64897985d-hdmqd 1/1 Running 0 4m32s kube-system etcd-minikube 1/1 Running 0 4m46s kube-system kube-apiserver-minikube 1/1 Running 0 4m47s kube-system kube-controller-manager-minikube 1/1 Running 0 4m47s kube-system kube-proxy-nkzmn 1/1 Running 0 4m32s kube-system kube-scheduler-minikube 1/1 Running 0 4m45s kube-system storage-provisioner 1/1 Running 1 <span class="o">(</span>4m2s ago<span class="o">)</span> 4m44s </code></pre> </div> <p>Cooooool!</p> <p>Are you thinking this command is too complicated? Use the config below and you'll be happy:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nb">alias </span><span class="nv">kubectl</span><span class="o">=</span><span class="s2">"minikube kubectl --"</span> </code></pre> </div> <p>I'm used to command-line to access the Kubernetes cluster. Yes, I never use the Kubernetes Dashboard. But if you like to have some insight into your cluster state with a Dashboard, <code>minikube</code> bundles it for you:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube dashboard 🔌 Enabling dashboard ... ▪ Using image kubernetesui/dashboard:v2.3.1 ▪ Using image kubernetesui/metrics-scraper:v1.0.7 🤔 Verifying dashboard health ... 🚀 Launching proxy ... 🤔 Verifying proxy health ... 🎉 Opening http://127.0.0.1:51801/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ <span class="k">in </span>your default browser... </code></pre> </div> <p>At this time, your browser will automatically open a new tab, and the Dashboard will soon show up:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--o7pdv2c2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0hix1h7jt5zcodfeeme.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--o7pdv2c2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0hix1h7jt5zcodfeeme.png" alt="Dashboard" width="880" height="550"></a></p> <h3> Deploy an Application in Your Shiny Kubernetes Cluster </h3> <p>Let’s create a sample deployment with the image <code>preslavmihaylov/kubehelloworld:latest</code> and expose it on port 3000.</p> <p>The <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/start/">minikube start</a> told us to use the <code>k8s.gcr.io/echoserver:1.4</code> to launch the <code>hello-minikube</code> application, but it'll fail on a Mac with m1 chip. See <a href="proxy.php?url=https://github.com/kubernetes/minikube/issues/11107">this issue</a> for more details.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl create deployment sample-deploy <span class="nt">--image</span><span class="o">=</span>preslavmihaylov/kubehelloworld:latest deployment.apps/sample-deploy created <span class="nv">$ </span>kubectl expose deployment sample-deploy <span class="nt">--type</span><span class="o">=</span>NodePort <span class="nt">--port</span><span class="o">=</span>3000 service/sample-deploy exposed <span class="nv">$ </span>kubectl get pod NAME READY STATUS RESTARTS AGE sample-deploy-795887588d-flkgb 1/1 Running 0 50s <span class="nv">$ </span>kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT<span class="o">(</span>S<span class="o">)</span> AGE kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 66m sample-deploy NodePort 10.107.59.38 &lt;none&gt; 3000:32631/TCP 43s </code></pre> </div> <p>Then we can use <code>kubectl</code> to forward the port:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl port-forward service/sample-deploy 3000:3000 Forwarding from 127.0.0.1:3000 -&gt; 3000 Forwarding from <span class="o">[</span>::1]:3000 -&gt; 3000 </code></pre> </div> <p>Open the browser, visit <code>localhost:3000</code>, and "Hello World!" will soon show up.</p> <p>Another way to access this service is to use the <code>minikube service sample-deploy</code> command and minikube will launch a browser page for you with the right <code>url:port</code> address.</p> <p>And we can use the command <code>minikube tunnel</code> to use the <code>LoadBalancer</code> type Service. Here is an example:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl delete service sample-deploy service <span class="s2">"sample-deploy"</span> deleted <span class="nv">$ </span>kubectl expose deployment sample-deploy <span class="nt">--type</span><span class="o">=</span>LoadBalancer <span class="nt">--port</span><span class="o">=</span>3000 service/sample-deploy exposed </code></pre> </div> <p>In another terminal window, start the tunnel to create a routable IP with the command <code>minikube tunnel</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube tunnel ✅ Tunnel successfully started 📌 NOTE: Please <span class="k">do </span>not close this terminal as this process must stay alive <span class="k">for </span>the tunnel to be accessible ... 🏃 Starting tunnel <span class="k">for </span>service sample-deploy. </code></pre> </div> <p>Now you can use the <code>EXTERNAL-IP</code> to access the Service:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT<span class="o">(</span>S<span class="o">)</span> AGE kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 86m sample-deploy LoadBalancer 10.96.205.167 127.0.0.1 3000:31945/TCP 28s <span class="nv">$ </span>curl 127.0.0.1:3000 Hello World! </code></pre> </div> <h2> How to Manage the Cluster </h2> <p>I'll show you some commands here:</p> <ul> <li> <code>minikube pause</code> # Pause Kubernetes cluster without impacting any deployed resources.</li> <li> <code>minikube unpause</code> # Unpause the Paused Kubernetes cluster.</li> <li> <code>minikube stop</code> # Remember the <code>minikube start</code>? Need I say more?</li> <li> <code>minikube config set memory 20480</code> # Change the default memory limit (requires a restart).</li> <li> <code>minikube addons list</code> # Browse the easily installed Kubernetes addons catalog.</li> <li> <code>minikube start -p aged --kubernetes-version=v1.16.1</code> # Create a second cluster running an older Kubernetes version.</li> <li> <code>minikube delete --all</code> # Yeah, delete all, delete anything, delete all clusters.</li> </ul> <p>That's all?</p> <p>No! But if you want to know them all, I'll show you the <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/commands/">command docs here</a>.</p> <h2> How to Push Images into a <code>minikube</code> Cluster </h2> <p>There are about 8 ways you can use to push images into a <code>minikube</code> cluster. And I'll show you the most useful two ways today.</p> <h3> Push Images with the <code>cache</code> Command </h3> <ul> <li> <code>minikube cache add [flags] [options]</code> # Add an image to local cache.</li> </ul> <p>Cache? Yes, the image will be cached by minikube and automatically pushed into all clusters, not like the <code>Kind</code> method: <code>kind load docker-image &lt;IMAGE&gt; [IMAGE...] [flags]</code>.</p> <p>Let's see how many sub-commands <code>cache</code> has:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube cache <span class="nt">-h</span> Add, delete, or push a <span class="nb">local </span>image into minikube Available Commands: add Add an image to <span class="nb">local </span>cache. delete Delete an image from the <span class="nb">local </span>cache. list List all available images from the <span class="nb">local </span>cache. reload reload cached images. </code></pre> </div> <p>Easy, isn't it?</p> <p>The only sub-command we need to talk about might be <code>reload</code>. If your image changed after you had cached it, you need to execute <code>minikube cache reload [flags] [options]</code></p> <p>E.g.:</p> <ul> <li> <code>minikube cache add alpine:latest</code> # Cache alpine:latest for all clusters.</li> <li> <code>minikube cache reload alpine:latest</code> # Reload alpine:latest if it's changed.</li> <li> <code>minikube cache list</code> # Display what images we have added to the cache.</li> <li> <code>minikube cache delete alpine:latest</code> # Just delete alpine:latest</li> </ul> <h3> Pushing Directly to the In-Cluster Docker Daemon </h3> <p>If the image we want to use inside the <code>minikube</code> cluster is built locally instead of pulled from a registry, we can reuse the Docker daemon inside the <code>minikube</code> cluster. Yeah, we don’t have to build the image on our host machine and <code>cache</code> it; we can just build inside the same docker daemon inside the <code>minikube</code> cluster.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 170fe8252660 kicbase/stable:v0.0.30 <span class="s2">"/usr/local/bin/entr…"</span> 6 hours ago Up 6 hours 127.0.0.1:50981-&gt;22/tcp, 127.0.0.1:50982-&gt;2376/tcp, 127.0.0.1:50984-&gt;5000/tcp, 127.0.0.1:50985-&gt;8443/tcp, 127.0.0.1:50983-&gt;32443/tcp minikube <span class="nv">$ </span><span class="nb">eval</span> <span class="si">$(</span>minikube docker-env<span class="si">)</span> <span class="nv">$ </span>docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES de4fedaf4ef0 preslavmihaylov/kubehelloworld <span class="s2">"docker-entrypoint.s…"</span> 5 hours ago Up 5 hours k8s_kubehelloworld_sample-deploy-795887588d-flkgb_default_2e6b194a-bdc7-44cc-b455-91959a4206a9_0 d45474ce1888 k8s.gcr.io/pause:3.6 <span class="s2">"/pause"</span> 5 hours ago Up 5 hours k8s_POD_sample-deploy-795887588d-flkgb_default_2e6b194a-bdc7-44cc-b455-91959a4206a9_0 ... </code></pre> </div> <p>What happened?</p> <p>After the command <code>eval $(minikube docker-env)</code> is executed, <code>docker</code> accesses the docker daemon inside the <code>minikube</code> cluster! What's the secret with <code>minikube docker-env</code> command? Let's dig into it:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>minikube docker-env <span class="nb">export </span><span class="nv">DOCKER_TLS_VERIFY</span><span class="o">=</span><span class="s2">"1"</span> <span class="nb">export </span><span class="nv">DOCKER_HOST</span><span class="o">=</span><span class="s2">"tcp://127.0.0.1:50982"</span> <span class="nb">export </span><span class="nv">DOCKER_CERT_PATH</span><span class="o">=</span><span class="s2">"/Users/danielhu/.minikube/certs"</span> <span class="nb">export </span><span class="nv">MINIKUBE_ACTIVE_DOCKERD</span><span class="o">=</span><span class="s2">"minikube"</span> <span class="c"># To point your shell to minikube's docker-daemon, run:</span> <span class="c"># eval $(minikube -p minikube docker-env)</span> </code></pre> </div> <p>I don't think I need to explain more about what happened. And we can <code>build</code> against the Docker inside <code>minikube</code> now. Just execute a command like <code>docker build -t my-image:latest .</code>, <code>my-image:latest</code> is instantly accessible to the Kubernetes cluster. </p> <h2> How to … </h2> <p>Oops, there are many how-tos we could talk about, but I believe that you can start using <code>minikube</code> now. Maybe I'll talk about more how-tos next time. If you want to dive into <code>minikube</code> now, go ahead and study the <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/">official documentation</a>.</p> <h2> Summary </h2> <p>Cool, I finished writing this blog, It's like opening Pandora's box; I love this feeling. Of course, I know this blog is at the primary-school-student level, but I believe I can do better and better.</p> <p>Don’t forget to give a Like, Comment, and Subscribe! I’ll see you in the next article!</p> minikube kubernetes devstream Creating a Local Kubernetes Cluster from the Ground Up Tiexin Guo Mon, 18 Apr 2022 08:00:25 +0000 https://dev.to/devstream/creating-a-local-kubernetes-cluster-from-the-ground-up-1pln https://dev.to/devstream/creating-a-local-kubernetes-cluster-from-the-ground-up-1pln <p>From the ground up? Yep, from the ground up!</p> <h2> Overview </h2> <p>Creating a Kubernetes cluster can be tricky.</p> <p>There are multiple tools designed just for that job. There are even companies that provide "installing K8s" as a service. And, to create a production-ready Kubernetes cluster with all the best practices in mind requires detailed designing and planning.</p> <p>So, the scope of this article isn't to help you to create a "production-ready" cluster.</p> <p>However, after reading this article, you should be able to create a local "testing" Kubernetes cluster, and for the developing and testing of DevStream, it should more than suffice.</p> <p>Even for a local testing cluster, there are multiple tools you can choose from. For example, there is <a href="proxy.php?url=https://minikube.sigs.k8s.io/docs/start/"><code>minikube</code></a>, and there is <a href="proxy.php?url=https://kind.sigs.k8s.io/"><code>kind</code></a>. <code>kind</code> is a tool for running local Kubernetes clusters using Docker container “nodes”. <code>kind</code> was primarily designed for testing Kubernetes.</p> <p>In this article, we are going with <code>kind</code>. We are not opinionated; we are not saying that <code>kind</code> is better than <code>minikube</code> or vice versa; we are merely choosing a tool to get the job done. If you are more familiar with other tools, it's completely fine!</p> <p>This article uses macOS as our local development environment. If you are using Windows or Linux, you can still read this post to get a general idea and achieve the same.</p> <h2> Install Docker </h2> <p>Docker works in a way using Linux's Namespace and Cgroup. It's quite easy to install Docker on Linux. On macOS and Windows, Docker runs with virtualization. However, we do not need to worry too much detail here, because it's quite simple to download and run Docker Desktop.</p> <p>Go to <a href="proxy.php?url=https://www.docker.com/products/docker-desktop">https://www.docker.com/products/docker-desktop</a>, find the correct version of Docker Desktop (Intel/amd64, or M1/arm64):</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--SS1298EH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3fw4v92l2jkjtu10xgz.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--SS1298EH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3fw4v92l2jkjtu10xgz.png" alt="download docker" width="668" height="290"></a></p> <p>Double click on the <code>Docker.dmg</code> file, and we see the installation interface like the following:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--W-vfyW01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdttp64v2lwbx9i02xj9.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--W-vfyW01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdttp64v2lwbx9i02xj9.png" alt="docker dmg" width="345" height="163"></a><br> Simply drag "Docker" to our "Applications," and within a few seconds, it's done! We can start it from the Launchpad:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Xtn8goTD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfxe23duywkakt3anvtr.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Xtn8goTD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfxe23duywkakt3anvtr.png" alt="install docker desktop" width="476" height="305"></a><br> Wait a few seconds and we can see the starting page:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--_U5ihUDj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjbkcnhpcubn32otkho9.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--_U5ihUDj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjbkcnhpcubn32otkho9.png" alt="docker settings" width="415" height="233"></a><br> Click the “gear" ⚙️ icon to change settings about Docker Desktop. For example, if we need to run a lot of containers, we might need to increase the memory. Here, we changed the memory to 4.00 GB:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--l4iFPocO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4nk1zplilmys84epgq4.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--l4iFPocO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4nk1zplilmys84epgq4.png" alt="apply and restart docker" width="415" height="343"></a></p> <p>Remember to "Apply &amp; Restart" to ensure the changes are effective.</p> <h2> Introduction to <code>kind</code> </h2> <p><code>kind</code> (Kubernetes-in-docker) uses a Docker container as a "node" to deploy Kubernetes. It's mainly used for testing Kubernetes itself.</p> <p><code>kind</code> is simple, containing a command-line tool named <code>kind</code> and a Docker image which has Kubernetes and <code>systemd</code>. <code>kind</code> uses Docker on the host machine to create a container, which runs <code>systemd</code>, which in turn runs the container runtime, <code>kubelet</code>, and other Kubernetes components. So, we end up with a whole Kubernetes cluster in one container.</p> <p>Note that although in the explanation above, the Cluster is only a single node cluster, it's possible to create a multi-node Kubernetes cluster.</p> <h2> Creating a Kubernetes Cluster with Kind at the Click of a Button </h2> <ol> <li>clone DevStream's repo: <a href="proxy.php?url=https://github.com/devstream-io/devstream">https://github.com/devstream-io/devstream</a> </li> <li>cd devstream; <code>make e2e-up</code> </li> </ol> <p>That's it.</p> <p>If you check out the Makefile, you will see it actually runs a shell script that runs <code>kind</code> to create the cluster. </p> <p>However, you wouldn't be satisfied if we end this article right here, right now, would you.</p> <p>So, let's have a deep dive into <code>kind</code>. Fasten your seat belt, because we are gonna fly!</p> <h2> Creating a Kubernetes Cluster with Kind at Two Clicks of a Button </h2> <p>On GitHub, we can find the latest release of <code>kind</code>: <a href="proxy.php?url=https://github.com/kubernetes-sigs/kind/releases">https://github.com/kubernetes-sigs/kind/releases</a>.</p> <p>Choose relatively new versions, and install:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="c"># method 1: download pre-compiled binary</span> <span class="nb">cd</span> /tmp curl <span class="nt">-Lo</span> ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.12.0/kind-darwin-arm64 <span class="nb">chmod</span> +x ./kind <span class="nb">sudo mv </span>kind /usr/local/bin/ <span class="c"># method 2: go get and compile locally</span> go get sigs.k8s.io/[email protected] </code></pre> </div> <p>We can also download the Docker image beforehand. Here we choose v1.22 of Kubernetes:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>kindest/node:v1.22.0@sha256:b8bda84bb3a190e6e028b1760d277454a72267a5454b57db34437c34a588d047 </code></pre> </div> <p>Create cluster:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>kind create cluster <span class="nt">--image</span><span class="o">=</span>kindest/node:v1.22.0 <span class="nt">--name</span><span class="o">=</span>dev </code></pre> </div> <p>Sample output:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Creating cluster <span class="s2">"dev"</span> ... ✓ Ensuring node image <span class="o">(</span>kindest/node:v1.22.0<span class="o">)</span> 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to <span class="s2">"kind-dev"</span> You can now use your cluster with: kubectl cluster-info <span class="nt">--context</span> kind-dev Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 </code></pre> </div> <p>Follow the hints above, next, let's run <code>kubectl cluster-info --context kind-dev</code> to switch the context and make sure you are in the right Kubernetes context.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl get node NAME STATUS ROLES AGE VERSION dev-control-plane Ready control-plane,master 7m4s v1.22.0 <span class="nv">$ </span>kubectl get pod <span class="nt">-n</span> kube-system NAME READY STATUS RESTARTS AGE coredns-78fcd69978-hch75 1/1 Running 0 10m coredns-78fcd69978-ztqn4 1/1 Running 0 10m etcd-dev-control-plane 1/1 Running 0 10m kindnet-l8qxq 1/1 Running 0 10m kube-apiserver-dev-control-plane 1/1 Running 0 10m kube-controller-manager-dev-control-plane 1/1 Running 0 10m kube-proxy-mzfgc 1/1 Running 0 10m kube-scheduler-dev-control-plane 1/1 Running 0 10m </code></pre> </div> <p>Now we have a local cluster for testing and developing Kubernetes.</p> <h2> Creating a Kubernetes Cluster with Kind at Three Clicks of a Button </h2> <p>A minimum highly-available Kubernetes cluster is composed of 3 nodes. In this section, let's see how to create a multi-node, highly-available cluster locally using <code>kind</code>.</p> <h3> <code>kind</code> Cluster Config </h3> <p>We can pass a config file to <code>kind</code> by using the <code>--config</code> parameter. Let's have a look at a <code>kind</code> config file:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="c1"># this config file contains all config fields with comments</span> <span class="c1"># NOTE: this is not a particularly useful config file</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="c1"># patch the generated kubeadm config with some extra settings</span> <span class="na">kubeadmConfigPatches</span><span class="pi">:</span> <span class="pi">-</span> <span class="pi">|</span> <span class="s">apiVersion: kubelet.config.k8s.io/v1beta1</span> <span class="s">kind: KubeletConfiguration</span> <span class="s">evictionHard:</span> <span class="s">nodefs.available: "0%"</span> <span class="c1"># patch it further using a JSON 6902 patch</span> <span class="na">kubeadmConfigPatchesJSON6902</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">group</span><span class="pi">:</span> <span class="s">kubeadm.k8s.io</span> <span class="na">version</span><span class="pi">:</span> <span class="s">v1beta2</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterConfiguration</span> <span class="na">patch</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">- op: add</span> <span class="s">path: /apiServer/certSANs/-</span> <span class="s">value: my-hostname</span> <span class="c1"># 1 control plane node and 3 workers</span> <span class="na">nodes</span><span class="pi">:</span> <span class="c1"># the control plane node config</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="c1"># the three workers</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> </code></pre> </div> <p>We can see that the config has two sections: the upper part being <code>kubeadm</code> related stuff and the lower part being nodes related settings. Apparently, the "nodes" part is where we are going to edit to achieve a multi-node cluster.</p> <h3> 1 Control Plane Node, 3 Worker Nodes Cluster </h3> <p>Let's create a config named <code>multi-node-config.yaml</code> with the following content:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="na">nodes</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> </code></pre> </div> <p>Then run:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kind create cluster <span class="nt">--config</span> multi-node-config.yaml <span class="se">\</span> <span class="nt">--image</span><span class="o">=</span>kindest/node:v1.22.0 <span class="nt">--name</span><span class="o">=</span>dev4 </code></pre> </div> <p>We will get some output similar to the previous single-node cluster output, except for the "Joining worker nodes" part:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Creating cluster <span class="s2">"dev4"</span> ... ✓ Ensuring node image <span class="o">(</span>kindest/node:v1.22.0<span class="o">)</span> 🖼 ✓ Preparing nodes 📦 📦 📦 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 ✓ Joining worker nodes 🚜 Set kubectl context to <span class="s2">"kind-dev4"</span> You can now use your cluster with: kubectl cluster-info <span class="nt">--context</span> kind-dev4 Thanks <span class="k">for </span>using kind! 😊 </code></pre> </div> <p>Let's run the following command to check out our new cluster:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl cluster-info <span class="nt">--context</span> kind-dev4 Kubernetes control plane is running at https://127.0.0.1:51851 CoreDNS is running at https://127.0.0.1:51851/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use <span class="s1">'kubectl cluster-info dump'</span><span class="nb">.</span> <span class="nv">$ </span>kubectl get node NAME STATUS ROLES AGE VERSION dev4-control-plane Ready control-plane,master 3m28s v1.22.0 dev4-worker Ready &lt;none&gt; 2m54s v1.22.0 dev4-worker2 Ready &lt;none&gt; 2m54s v1.22.0 dev4-worker3 Ready &lt;none&gt; 2m54s v1.22.0 </code></pre> </div> <p>From the result above, we can see that this cluster has 1 control plane node and 3 worker nodes.</p> <h3> 3 Control Plane Nodes, 3 Worker Nodes, Highly Available Cluster </h3> <p><em>Note: "Highly available" here only means that we have three control plane nodes. It's not strictly "highly available" because apparently, the three control plane nodes are actually on the same host, so when the host is down, everything is gone.</em></p> <p>Prepare the <code>ha-config.yaml</code> with the following content:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="na">nodes</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">worker</span> </code></pre> </div> <p>Run:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kind create cluster <span class="nt">--config</span> ha-config.yaml <span class="se">\</span> <span class="nt">--image</span><span class="o">=</span>kindest/node:v1.22.0 <span class="nt">--name</span><span class="o">=</span>dev6 </code></pre> </div> <p>We can see familiar outputs, with the exception being "Configuring the external load balancer” and “Joining more control-plane nodes":<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>Creating cluster <span class="s2">"dev6"</span> ... ✓ Ensuring node image <span class="o">(</span>kindest/node:v1.22.0<span class="o">)</span> 🖼 ✓ Preparing nodes 📦 📦 📦 📦 📦 📦 ✓ Configuring the external load balancer ⚖️ ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 ✓ Joining more control-plane nodes 🎮 ✓ Joining worker nodes 🚜 Set kubectl context to <span class="s2">"kind-dev6"</span> You can now use your cluster with: kubectl cluster-info <span class="nt">--context</span> kind-dev6 Have a <span class="nb">nice </span>day! 👋 </code></pre> </div> <p>Some fun facts:</p> <ul> <li>the number of boxes after "Preparing nodes" equals the number of nodes</li> <li>the final greeting message is different: it was "Thanks for using kind! 😊" previously and now it's "Have a nice day! 👋"</li> </ul> <p>Heck, those <code>kind</code> developers sure went some extra miles to enhance the user experience!</p> <p>Check the cluster:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>kubectl cluster-info <span class="nt">--context</span> kind-dev6 Kubernetes control plane is running at https://127.0.0.1:52937 CoreDNS is running at https://127.0.0.1:52937/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use <span class="s1">'kubectl cluster-info dump'</span><span class="nb">.</span> <span class="nv">$ </span>kubectl get node NAME STATUS ROLES AGE VERSION dev6-control-plane Ready control-plane,master 8m19s v1.22.0 dev6-control-plane2 Ready control-plane,master 7m46s v1.22.0 dev6-control-plane3 Ready control-plane,master 7m20s v1.22.0 dev6-worker Ready &lt;none&gt; 7m v1.22.0 dev6-worker2 Ready &lt;none&gt; 7m v1.22.0 dev6-worker3 Ready &lt;none&gt; 7m v1.22.0 </code></pre> </div> <p>We can see this cluster has 3 control plane nodes.</p> <p>So far, we have mastered how to use <code>kind</code> to create a multi-node Kubernetes cluster locally.</p> <h2> Advanced <code>kind</code> Features </h2> <p>Now that we know how to create clusters using <code>kind</code>, let's have a look at some advanced operations which could help you better use the clusters.</p> <h3> Port Mapping </h3> <p>Imagine you are running an Nginx container listening on port 8080 in a <code>kind</code> cluster but you wish the outside world (outside of the cluster) to access the Nginx port. To achieve this, we can add the <code>extraPortMappings</code> configuration:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="na">nodes</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span> <span class="na">extraPortMappings</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8080</span> <span class="na">hostPort</span><span class="pi">:</span> <span class="m">8080</span> <span class="na">listenAddress</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0.0.0.0"</span> <span class="na">protocol</span><span class="pi">:</span> <span class="s">tcp</span> </code></pre> </div> <p>In this way, the port on the pod 8080 is mapped to the port on the host.</p> <h3> Expose <code>kube-apiserver</code> </h3> <p>Sometimes we want to install Kubernetes with <code>kind</code> on one host, but access the cluster from another host. By default, the <code>kube-apiserver</code> installed by <code>kind</code> listens on 127.0.0.1 (with a random port.) To make the <code>kind</code> cluster accessible from another host, we need to make <code>kube-apiserver</code> listen on a network interface (for example, eth0.)</p> <p>In the config file, we add <code>networking.apiServerAddress</code>. The IP is your local nic's IP:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="na">networking</span><span class="pi">:</span> <span class="na">apiServerAddress</span><span class="pi">:</span> <span class="s2">"</span><span class="s">192.168.39.1"</span> </code></pre> </div> <h3> Enable Feature Gates </h3> <p>Feature gates are a set of <code>key=value</code> pairs that describe Kubernetes features that are only available in Alpha, Beta or GA stage.</p> <p>If we want to try some of those features, we can enable Feature Gates. In the config file, use:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span> <span class="na">featureGates</span><span class="pi">:</span> <span class="na">FeatureGateName</span><span class="pi">:</span> <span class="no">true</span> </code></pre> </div> <h3> Importing Images </h3> <p>Because the Kubernetes cluster is in fact running in a Docker container, by default, it can't access Docker images that are on the host. However, we can import those images from hosts to the Kubernetes cluster created by <code>kind</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="c"># import my-image:v1</span> kind load docker-image my-image:v1 <span class="nt">--name</span> dev <span class="c"># import my-image.tar</span> kind load image-archive my-image.tar <span class="nt">--name</span> dev </code></pre> </div> <p>With this method, when we are building a new Docker image on the host which we want to run in a <code>kind</code> Kubernetes cluster, we can:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>docker build <span class="nt">-t</span> my-image:v1 ./my-image-dir kind load docker-image my-image:v1 kubectl apply <span class="nt">-f</span> my-image.yaml </code></pre> </div> <p>How to see the images that are available in the <code>kind</code> Kubernetes cluster? Easy:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>docker <span class="nb">exec</span> <span class="nt">-it</span> dev-control-plane crictl images </code></pre> </div> <p><code>dev-control-plane</code> is the name of the <code>kind</code> cluster. </p> <p>You can also use <code>crictl -h</code> to see all the supported commands. For example, we can delete an image by using <code>crictl rmi &lt;image_name&gt;</code>.</p> <h2> Summary </h2> <p>Have fun playing with Kubernetes locally!</p> <p>If you like this article, please like, comment, and subscribe. See you in the next article!</p> devops cloud infrastructure kubernetes Dagger (the CI/CD Tool, not the Knife) In-Depth: Everything You Need to Know (as of Apr 2022) Tiexin Guo Tue, 12 Apr 2022 01:38:48 +0000 https://dev.to/devstream/dagger-the-cicd-tool-not-the-knife-in-depth-everything-you-need-to-know-as-of-apr-2022-3cfn https://dev.to/devstream/dagger-the-cicd-tool-not-the-knife-in-depth-everything-you-need-to-know-as-of-apr-2022-3cfn <h2> 1 What is Dagger? </h2> <p>TL;DR: Dagger runs your CI/CD pipelines locally in a Docker container, and can run the container in any CI environment (as long as that CI can run a container, of course.)</p> <p>Do you want the long answer to this million-dollar question? It's hard to answer, honestly. News is calling it a "DevOps platform"; the VC that funded Dagger even called it a "DevOps operating system."</p> <p>But, in fact, Dagger is neither of those things.</p> <p>Before we can answer what Dagger is, let's have a look at it in-depth:</p> <h2> 2 Quirks and Features </h2> <h3> 2.1 BuildKit </h3> <p>In Dagger, the configuration is executed in BuildKit, which is the execution engine at the heart of Docker.</p> <p>BuildKit was developed as part of the Moby project, the latter of which is an open framework to assemble specialized container systems without reinventing the wheel by Docker. Basically, it's a toolkit for converting source code to build artifacts in an efficient, expressive, and repeatable manner. It was announced in 2017 and began shipping with Docker Engine in 2018’s version 18.09.</p> <h3> 2.2 CUE </h3> <p>Unlike most popular CI systems out there, you don't write YAML in Dagger; you write CUE.</p> <p>I feel you because I didn't know what's CUE either. Turned out, CUE is an open-source data validation language and inference engine with its roots in logic programming.</p> <p>It aims to simplify tasks involving defining and using data. It's actually a superset of JSON, so users familiar with JSON should feel comfortable with it already and can get started quickly. It also has got built-in auto-formatting (yay.)</p> <p>Although the language is not a general-purpose programming language, it has many applications, such as data validation, data templating, configuration (that's probably why Dagger decided to use it in the first place), querying, code generation, and even scripting.</p> <h3> 2.3 Wait a Minute </h3> <p>Since it's already reusing Docker's parts for configuration execution, why not reuse Docker's other part, Dockerfile, for configuration?</p> <p>What's the purpose of using another language just for the configuration?</p> <p>Solomon Hykes, the founder of Dagger, actually answered exact this question on their official Discord channel:</p> <blockquote> <p>We needed a modern declarative language with a type system, a package manager, native yaml and json interop, a formal spec, and a standalone community not locked to one tool.</p> <p>Also Dockerfiles are specific to build, but Dagger is more general-purpose automation</p> <p>There was no way at all Dockerfile could support our requirements (speaking as one of the original authors of the Dockerfile syntax)</p> </blockquote> <h2> 3 Enough Tech Spec. What Does Dagger Do? </h2> <h3> 3.1 What Dagger Isn't </h3> <p>First, let me tell you what Dagger isn't by quoting the official documentation:</p> <blockquote> <p>Dagger does not replace your CI: it improves it by adding a portable development layer on top of it.</p> </blockquote> <p>OK, so it's not yet another CI (or CD, for that matter).</p> <p>Dagger didn't even try to <em>replace</em> your existing CI, at all. But rather, it <em>improves</em> your CI, by adding a wrapper layer.</p> <p>I know the term "wrapper" doesn't sound fancy, so let's call it by its official reference, and that is "portable development layer".</p> <h3> 3.2 So, Just a Wrapper? </h3> <p>Disappointed? Don't conclude too quick; follow me. First, let's look at some other DevOps/cloud related examples:</p> <ul> <li>Think of Terraform. You've got multiple environments to manage. Even with reuseable modules and roles, you still have duplicated code across envs. Then comes Terragrunt, which is a (thin) wrapper that provides extra tools for keeping your configurations simple without repeating yourself.</li> <li>Think of AWS CDK. It actually is a wrapper layer on top of CloudFormation, which lets you use your familiar programming languages to define and provision AWS cloud infrastructure, so that you don't have to deal with CloudFormation's non-human-readable configurations. Of course, your code still converts to a format that CloudFormation understands, and your infrastructure is still managed by CloudFormation; AWS CDK doesn't really interact with AWS APIs directly. That's why it's only a wrapper layer on top of CloudFormation.</li> <li>Think of CDKTF (CDK for Terraform); it's no different than AWS CDK, perhaps because CDKTF is inspired by AWS CDK and also uses AWS's <code>jsii</code> library to be polyglot. It's a wrapper layer on top of Terraform that translates your code into Terraform HCL so that you don't have to learn HCL. But in essence, your infrastructure is still managed by Terraform HCL, not your code directly. So, it's yet another wrapper.</li> </ul> <p>You must have already figured out where I am going with this, and you are right: yes, Dagger is no different. It is a wrapper.</p> <p>But, of course, the wrapper has to do something to be useful, right? Then what exactly does Dagger do? What exactly does Dagger wrap? Good Questions.</p> <h3> 3.3 What Dagger Can Do </h3> <p>In any CI system, you define some steps and actions in a certain format (YAML, most likely) and run it in your CI system. For example, in Jenkins, maybe you will write some groovy file. In GitHub Actions, you write some YAML with multiple steps.</p> <p>Basically, Dagger runs those "steps and actions" in a Docker container. Then where do you run the Dagger docker container itself? Great question: you can either run it locally (because you can install Docker desktop, right?) or in your existing CI (since most CIs can run a docker container.)</p> <p>If you think about it: Dagger doesn't wrap your CI pipelines or systems. It wraps those detailed steps and actions into a Docker container and still runs in your existing CI. It's like writing a big Dockerfile, and when you run the container, it does git clone, source code static scan, test, build, artifact upload, and what have you.</p> <h2> 4 What Dagger Really Is </h2> <p>Yes, Dagger is a wrapper, that part is true.</p> <p>But, it doesn't wrap CI systems; it wraps your pipeline steps and actions into a container (you have to rewrite those steps and actions in Dagger's syntax, though), and the wrapped result can run in another CI (as long as that CI can run a container.)</p> <p>In this sense, Dagger <em>is</em> yet another CI, except that CI runs in a container and most CI systems happen to be able to run containers.</p> <h2> 5 Benefits </h2> <p>I think there are 3 major advantages of using Dagger</p> <h3> 5.1 Local Development </h3> <p>Firstly, there is no need to install <em>any</em> dependencies specific to this application, because Dagger manages all the intermediate steps and transient dependencies inside the Docker container.</p> <p>This might not be an advantage if we are talking about CI, but it is an advantage if we are talking about local development.</p> <p>Think of Go where you have to install modules or think of Nodejs where you might even need to switch Node versions then do an NPM install. Now you can do all of those inside a container and only get the final result to your local laptop.</p> <h3> 5.2 "On-Premise" CI </h3> <p>You can run your pipeline locally now since you can easily have the Docker desktop up and running locally.</p> <p>I'm not sure if this is a solid requirement, though. Maybe it is? Because we all have powerful laptops now; why waste money on some CI systems when you can run them locally?</p> <p>The idea of running it anywhere as long as Docker is available is intriguing, though. If you don't want to buy CI as a service, you can run Dagger in your own infrastructure. </p> <h3> 5.3 Migrate to Another CI </h3> <p>Since your "steps and actions" now are running in a container, you can run it elsewhere, in another CI system.</p> <p>Should you need to migrate to another CI, you do not need to rewrite your CI steps anymore. For example, you don't want to use your company's old Jenkins instance anymore, but you are already using Dagger with Jenkins, and now you want to give GitHub Actions a try.</p> <p>It's worth noting that in this scenario, there are still two things to do:</p> <ul> <li>If you are using Jenkins now, and want to migrate those Jenkins pipelines into Dagger, you need to do it manually. The cost is the same (if not more) as rewriting your whole pipeline in GitHub Actions' syntax.</li> <li>You will still need to learn about your new CI system: how a job is triggered, the syntax, etc.</li> </ul> <p>Here you can see Dagger does provide a solution to avoid CI lock-in (to some extent.) But it is not a game changing solution that could resolve your flexibility concern. </p> <p>For a DevOps engineer, a DevOps toolchain that could accommodate different needs and priorities from different teams is more than appealing. That being said, each component is modular and pluggable, and you could free yourself from tedious work like launching, integrating, and managing all these pieces. </p> <p>Ideally, you could define your desired DevOps tools in a single human-readable YAML config file, and with one single command, you have your whole DevOps toolchain and SDLC workflow set up or changed. </p> <p>If you are intrigued by the simplicity of "DevOps toolchain as code", don't hesitate to check out DevStream <a href="proxy.php?url=https://github.com/devstream-io/devstream">here</a>. </p> <h2> 6 Should I use Dagger Now? </h2> <p>Is it promising? Maybe. Should I start using it now? My answer is No. Three reasons:</p> <ul> <li>The Dagger project itself still uses GitHub Actions. Why? Probably because it has limitations and can't do everything you can achieve with GitHub Actions.</li> <li>You probably won't change your CI system every 6 months. If you only change it one time in 4 years, why bother adding that wrapper?</li> <li>Dagger is only recently announced. It hasn't supported a whole lot of CI systems yet. Maybe the CI you want to switch to doesn't support it yet.</li> </ul> <p>Like, comment, subscribe. See you in the next article!</p> devops ci cd Creating a DevStream (dtm) Plugin for Anything Tiexin Guo Tue, 29 Mar 2022 09:27:10 +0000 https://dev.to/devstream/creating-a-devstream-dtm-plugin-for-anything-5725 https://dev.to/devstream/creating-a-devstream-dtm-plugin-for-anything-5725 <p>Yes, the title of this post isn't bluffing: you can actually create a plugin for just about anything that takes your fancy.</p> <blockquote> <p>In my previous article, I walked you guys through DevStream's codebase.</p> <p>If you haven't read it yet, here's a quick link for you:</p> <p><a href="proxy.php?url=https://blog.dtm.dev/post/2022-03/01-hello-world/">https://blog.dtm.dev/post/2022-03/01-hello-world/</a>.</p> </blockquote> <p>In this blog, we will walk you through the steps of creating a DevStream plugin from scratch with an example. </p> <h2> What is DevStream </h2> <p>DevStream is an amazing tool that lets you install, update, manage, and integrate your DevOps tools quickly and flexibly.</p> <p>Not to brag, but with DevStream, you can have your own DevOps toolchain that is specifically tailored to your need up and running in 5 minutes.</p> <p>Don't believe it? Check out our <a href="proxy.php?url=https://docs.dtm.dev/en/latest/">docs</a> and follow the quickstart guide!</p> <h2> Existing Plugins </h2> <p>At the moment of publishing this article, we already support the following tools:</p> <ul> <li>Trello (including integration with GitHub)</li> <li>Jira (integration with GitHub)</li> <li>GitHub Repository bootstrapping (for Go)</li> <li>GitHub Actions (for Go, Python, and Nodejs)</li> <li>GitLab CI (for Go)</li> <li>Jenkins (installation)</li> <li>ArgoCD</li> <li>ArgoCD Application (the deployment of your apps)</li> <li>Prometheus + Grafana</li> <li>DevLake</li> <li>OpenLDAP</li> </ul> <p>We aim to have 50 plugins at the end of 2022!</p> <p>Check out our <a href="proxy.php?url=https://github.com/merico-dev/stream">README</a> for the latest status.</p> <h2> Why Would I Want to Create a DevStream Plugin </h2> <p>Wait. YOU ALREADY HAVE TONS OF PLUGINS! Why on earth would I want to create yet another one?</p> <p>I agree with you.</p> <p>Can I get a show of hands, who here has made a DevStream plugin before?</p> <p>Very few, if any, I guess.</p> <p>However (I know it's just a fancy "but"), there are, in fact, things that you want to build a plugin for:</p> <ul> <li>Maybe you are building a nice and shiny DevOps tool, and you want to be able to set it up quickly without any hassle. You would have to write some automation scripts for it anyway, right? DevStream got you covered.</li> <li>Maybe you even need to integrate your tool with other tools to get the most out of it and you really don't want to reinvent a lot of wheels just to manage those boring integrations. Again, DevStream got you covered.</li> <li>Maybe you have both internal tools and open-source tools whose installation and integration need to be automated. The open-source tools are fine, but how to manage those internal ones and integrate them? DevStream got you covered.</li> </ul> <p>Or, maybe you just want to learn Go's plugin, become a contributor, join our community and earn your certification (maybe a small present, too, who knows.) No problem.</p> <p>No matter what your intention is and what thing you want to achieve, DevStream got you covered.</p> <p>So hang tight, let's get started.</p> <h2> Design: A "Local File" Plugin </h2> <p>In this example, let's build something dum but simple, just to show you the process of creating a plugin.</p> <p>We are creating a "local file" plugin. You specify the name and the content, and the plugin will create a local file for you. Let's decide how we are going to use this plugin:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">tools</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my-file</span> <span class="na">plugin</span><span class="pi">:</span> <span class="s">localfile</span> <span class="na">options</span><span class="pi">:</span> <span class="na">filename</span><span class="pi">:</span> <span class="s">foo.txt</span> <span class="na">content</span><span class="pi">:</span> <span class="s2">"</span><span class="s">hello,</span><span class="nv"> </span><span class="s">world"</span> </code></pre> </div> <p>Basically:</p> <ul> <li>the name of the plugin: <code>localfile</code> </li> <li>options of the plugin: <ul> <li>filename</li> <li>content</li> </ul> </li> </ul> <p>The plugin will create a file with desired content according to this piece of config.</p> <h2> Scaffolding Automagically </h2> <p>First, let's clone the DevStream repo and generate some scaffolding code:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>git clone [email protected]:merico-dev/stream.git <span class="nb">cd </span>stream <span class="c"># builds dtm locally to make sure it's using the same dependencies as your new plugin</span> make build-core ./dtm develop create-plugin <span class="nt">--name</span><span class="o">=</span>localfile </code></pre> </div> <p>There will be some useful output to guide you through the whole process.</p> <p>Now, if we do a <code>git status</code>, we can see some new stuff are already created automagically:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>git status On branch main Your branch is up to <span class="nb">date </span>with <span class="s1">'origin/main'</span><span class="nb">.</span> Untracked files: <span class="o">(</span>use <span class="s2">"git add &lt;file&gt;..."</span> to include <span class="k">in </span>what will be committed<span class="o">)</span> cmd/plugin/localfile/ docs/plugins/localfile.md internal/pkg/plugin/localfile/ </code></pre> </div> <p>Let's have a quick recap of the directory structure:</p> <h3> cmd/ </h3> <p><code>cmd/plugin/localfile/main.go</code> is the main entrance of your plugin. But here you don't need to do anything; nothing.</p> <p><code>dtm</code> has already generated the code for you, including the interfaces that you must implement.</p> <h3> docs/ </h3> <p><code>docs/plugins/localfile.md</code> is the automatically generated documentation.</p> <p>Yep, I know <code>dtm</code> is automagic, but it can't read your mind. I'm afraid that you will have to write your own doc.</p> <p>But hey, at least here you get a reminder that you need to create a doc :)</p> <h3> internal/pkg/ </h3> <p><code>internal/pkg/plugin/localfile</code> has your plugin's main logic.</p> <p>Here, we need to:</p> <ul> <li>define your input parameters (options);</li> <li>implement the validation of the input parameters;</li> <li>implement four mandatory interfaces.</li> </ul> <h2> Core Concepts </h2> <h3> Config/State/Resource </h3> <p>Before explaining interfaces and implementing them, let's have a look at how DevStream actually works:</p> <ul> <li> <em>Config</em> is a list of tools, each of which has a name, plugin, options, etc.</li> <li> <em>State</em> is a map containing each tool's name, plugin, options, etc. It's used to store the result of <code>dtm</code>'s last action.</li> <li> <em>Resource</em> is the tool that the plugin created. The <code>Read</code> interface returns a description of that resource, which should be the same as the <em>State</em> if nothing has been changed after <code>dtm</code>'s last action.</li> </ul> <p>DevStream decides what to do based on your <em>Config</em>, the <em>State</em>, and the <em>Resource</em>'s status. See the flowchart below:</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--w5riyc_n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.dtm.dev/img/config_state_resource.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--w5riyc_n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.dtm.dev/img/config_state_resource.png" alt="devstream config state resource" width="584" height="1052"></a></p> <h3> Interfaces </h3> <p>A quick recap: each DevStream plugin must satisfy the following four interfaces:</p> <ul> <li><code>Create</code></li> <li><code>Read</code></li> <li><code>Update</code></li> <li><code>Delete</code></li> </ul> <p>Return values:</p> <ul> <li> <code>Create</code> and <code>Update</code> return two values <code>(map[string]interface{}, error)</code>. The first return value is considered as the <em>State</em>, which will be stored in DevStream's state file.</li> <li> <code>Read</code>'s first return value is a description of the <em>Resource</em>, which should be the same as the <em>State</em> if nothing has changed.</li> <li> <code>Delete</code> returns <code>(true, nil)</code> if there is no error; otherwise it returns <code>(false, error)</code>.</li> </ul> <h2> Coding </h2> <h3> Input Options/Validation </h3> <p>Now let's open <code>internal/pkg/plugin/localfile/options.go</code> and add options according to our design in the previous section.</p> <p>The <code>internal/pkg/plugin/localfile/options.go</code> should look like:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="c">// Options is the struct for configurations of the localfile plugin.</span> <span class="k">type</span> <span class="n">Options</span> <span class="k">struct</span> <span class="p">{</span> <span class="n">Filename</span> <span class="kt">string</span> <span class="n">Content</span> <span class="kt">string</span> <span class="p">}</span> </code></pre> </div> <p>Let's also implement the validation function of the input options.</p> <p>Open <code>internal/pkg/plugin/localfile/validate.go</code> and change the logic to verify the options. It should look like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">import</span> <span class="s">"fmt"</span> <span class="c">// validate validates the options provided by the core.</span> <span class="k">func</span> <span class="n">validate</span><span class="p">(</span><span class="n">options</span> <span class="o">*</span><span class="n">Options</span><span class="p">)</span> <span class="p">[]</span><span class="kt">error</span> <span class="p">{</span> <span class="n">res</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">error</span><span class="p">,</span> <span class="m">0</span><span class="p">)</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">Filename</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span> <span class="n">res</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"filename does not exist"</span><span class="p">))</span> <span class="p">}</span> <span class="k">return</span> <span class="n">res</span> <span class="p">}</span> </code></pre> </div> <h3> Create() </h3> <p>We want to create the file based on the input options. So, let's do just that in the file <code>internal/pkg/plugin/localfile/create.go</code>.</p> <p>OK, talk is cheap, show me the code:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"github.com/mitchellh/mapstructure"</span> <span class="s">"github.com/merico-dev/stream/pkg/util/log"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">Create</span><span class="p">(</span><span class="n">options</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{})</span> <span class="p">(</span><span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">opts</span> <span class="n">Options</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">mapstructure</span><span class="o">.</span><span class="n">Decode</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="k">if</span> <span class="n">errs</span> <span class="o">:=</span> <span class="n">validate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="nb">len</span><span class="p">(</span><span class="n">errs</span><span class="p">)</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">e</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">errs</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"Options error: %s."</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="p">}</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"opts are illegal"</span><span class="p">)</span> <span class="p">}</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">writefile</span><span class="p">(</span><span class="n">opts</span><span class="o">.</span><span class="n">Filename</span><span class="p">,</span> <span class="n">opts</span><span class="o">.</span><span class="n">Content</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="n">status</span> <span class="o">:=</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{}{</span> <span class="s">"filename"</span><span class="o">:</span> <span class="n">opts</span><span class="o">.</span><span class="n">Filename</span><span class="p">,</span> <span class="s">"content"</span><span class="o">:</span> <span class="n">opts</span><span class="o">.</span><span class="n">Content</span><span class="p">,</span> <span class="p">}</span> <span class="k">return</span> <span class="n">status</span><span class="p">,</span> <span class="no">nil</span> <span class="p">}</span> </code></pre> </div> <p>If you compare the code above to the code generated by <code>dtm develop</code>, you will see that we basically did nothing here. We only filled some blanks which are marked by <code>dtm</code>'s comment. The same is true for all the other interfaces.</p> <p><em>A small tip: here, we can put the function <code>writefile</code> in <code>internal/pkg/plugin/localfile/localfile.go</code>, so that the code is better organized.</em></p> <p>The <code>localfile.go</code> will look like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">import</span> <span class="s">"os"</span> <span class="k">func</span> <span class="n">writefile</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">content</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span> <span class="n">f</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Create</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="n">err</span> <span class="p">}</span> <span class="k">defer</span> <span class="n">f</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">WriteString</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="n">err</span> <span class="p">}</span> <span class="k">return</span> <span class="no">nil</span> <span class="p">}</span> </code></pre> </div> <h3> Read() </h3> <p>Let's also implement the <code>Read</code> interface.</p> <p>The logic is simple: we want to try to see if the file exists or not, and if yes, what's the filename and content.</p> <p><code>internal/pkg/plugin/localfile/read.go</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"os"</span> <span class="s">"strings"</span> <span class="s">"github.com/mitchellh/mapstructure"</span> <span class="s">"github.com/merico-dev/stream/pkg/util/log"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">Read</span><span class="p">(</span><span class="n">options</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{})</span> <span class="p">(</span><span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">opts</span> <span class="n">Options</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">mapstructure</span><span class="o">.</span><span class="n">Decode</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="k">if</span> <span class="n">errs</span> <span class="o">:=</span> <span class="n">validate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="nb">len</span><span class="p">(</span><span class="n">errs</span><span class="p">)</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">e</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">errs</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"Options error: %s."</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="p">}</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"opts are illegal"</span><span class="p">)</span> <span class="p">}</span> <span class="n">content</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">opts</span><span class="o">.</span><span class="n">Filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="s">"no such file or directory"</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="no">nil</span> <span class="p">}</span> <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="n">status</span> <span class="o">:=</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{}{</span> <span class="s">"filename"</span><span class="o">:</span> <span class="n">opts</span><span class="o">.</span><span class="n">Filename</span><span class="p">,</span> <span class="s">"content"</span><span class="o">:</span> <span class="kt">string</span><span class="p">(</span><span class="n">content</span><span class="p">),</span> <span class="p">}</span> <span class="k">return</span> <span class="n">status</span><span class="p">,</span> <span class="no">nil</span> <span class="p">}</span> </code></pre> </div> <p>On the return value:</p> <ul> <li>If the file doesn't exist, return nil, no error, because it means the resource hasn't been created yet (or deleted by somebody)</li> <li>If the file exists, return the "status" of it and no error.</li> <li>Otherwise, return nil and the error.</li> </ul> <h3> Update() </h3> <p><code>Update</code> will be triggered if <code>Read</code> returns a different result than what's recorded in the <em>State</em>.</p> <p>For the implementation, since we are updating a file, it's the same as <code>Create</code>, so we can actually reuse it here without duplicated code:</p> <p><code>internal/pkg/plugin/localfile/update.go</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">func</span> <span class="n">Update</span><span class="p">(</span><span class="n">options</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{})</span> <span class="p">(</span><span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">Create</span><span class="p">(</span><span class="n">options</span><span class="p">)</span> <span class="p">}</span> </code></pre> </div> <h3> Delete() </h3> <p>Last but not least, let's implement the <code>Delete</code> interface.</p> <p><code>internal/pkg/plugin/localfile/delete.go</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">localfile</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"os"</span> <span class="s">"github.com/mitchellh/mapstructure"</span> <span class="s">"github.com/merico-dev/stream/pkg/util/log"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">Delete</span><span class="p">(</span><span class="n">options</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{})</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">opts</span> <span class="n">Options</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">mapstructure</span><span class="o">.</span><span class="n">Decode</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="no">false</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="k">if</span> <span class="n">errs</span> <span class="o">:=</span> <span class="n">validate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">opts</span><span class="p">);</span> <span class="nb">len</span><span class="p">(</span><span class="n">errs</span><span class="p">)</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">e</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">errs</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"Options error: %s."</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="p">}</span> <span class="k">return</span> <span class="no">false</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"opts are illegal"</span><span class="p">)</span> <span class="p">}</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">opts</span><span class="o">.</span><span class="n">Filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="no">false</span><span class="p">,</span> <span class="n">err</span> <span class="p">}</span> <span class="k">return</span> <span class="no">true</span><span class="p">,</span> <span class="no">nil</span> <span class="p">}</span> </code></pre> </div> <p>Just delete the file; nothing to look at here.</p> <h2> Build </h2> <p>Now that we finished coding, let's build our new plugin. Our Makefile now supports building a specific plugin only:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>make build-plugin.localfile </code></pre> </div> <h2> Test </h2> <p>First, let's create a config file <code>config-localfile-test.yaml</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">tools</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my-file</span> <span class="na">plugin</span><span class="pi">:</span> <span class="s">localfile</span> <span class="na">options</span><span class="pi">:</span> <span class="na">filename</span><span class="pi">:</span> <span class="s">foo.txt</span> <span class="na">content</span><span class="pi">:</span> <span class="s2">"</span><span class="s">hello,</span><span class="nv"> </span><span class="s">world"</span> </code></pre> </div> <p>Now, let's <code>apply</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./dtm <span class="nt">-y</span> apply <span class="nt">-f</span> config-localfile-test.yaml 2022-03-29 11:25:17 ℹ <span class="o">[</span>INFO] Apply started. 2022-03-29 11:25:17 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-03-29 11:25:17 ℹ <span class="o">[</span>INFO] Tool my-file <span class="o">(</span>localfile<span class="o">)</span> found <span class="k">in </span>config but doesn<span class="s1">'t exist in the state, will be created. 2022-03-29 11:25:17 ℹ [INFO] Start executing the plan. 2022-03-29 11:25:17 ℹ [INFO] Changes count: 1. 2022-03-29 11:25:17 ℹ [INFO] -------------------- [ Processing progress: 1/1. ] -------------------- 2022-03-29 11:25:17 ℹ [INFO] Processing: my-file (localfile) -&gt; Create ... 2022-03-29 11:25:17 ✔ [SUCCESS] Plugin my-file(localfile) Create done. 2022-03-29 11:25:17 ℹ [INFO] -------------------- [ Processing done. ] -------------------- 2022-03-29 11:25:17 ✔ [SUCCESS] All plugins applied successfully. 2022-03-29 11:25:17 ✔ [SUCCESS] Apply finished. </span></code></pre> </div> <p>As we can see, the plugin has created our file successfully. To verify, you can open the "foo.txt" file to have a look.</p> <p>If we <code>apply</code> again, nothing should happen, since the file is already created with the desired content:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./dtm <span class="nt">-y</span> apply <span class="nt">-f</span> config-localfile-test.yaml 2022-03-29 11:30:43 ℹ <span class="o">[</span>INFO] Apply started. 2022-03-29 11:30:43 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-03-29 11:30:45 ℹ <span class="o">[</span>INFO] No changes <span class="k">done </span>since last apply. There is nothing to <span class="k">do</span><span class="nb">.</span> 2022-03-29 11:30:45 ✔ <span class="o">[</span>SUCCESS] Apply finished. </code></pre> </div> <p>But, what if somebody changed the content of "foo.txt"? Let's experiment:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"changed"</span> <span class="o">&gt;</span> foo.txt <span class="nv">$ </span>./dtm <span class="nt">-y</span> apply <span class="nt">-f</span> config-localfile-test.yaml 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Apply started. 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Tool my-file <span class="o">(</span>localfile<span class="o">)</span> drifted from the state, will be updated. 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Start executing the plan. 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Changes count: 1. 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] <span class="nt">--------------------</span> <span class="o">[</span> Processing progress: 1/1. <span class="o">]</span> <span class="nt">--------------------</span> 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] Processing: my-file <span class="o">(</span>localfile<span class="o">)</span> -&gt; Update ... 2022-03-29 11:26:40 ✔ <span class="o">[</span>SUCCESS] Plugin my-file<span class="o">(</span>localfile<span class="o">)</span> Update <span class="k">done</span><span class="nb">.</span> 2022-03-29 11:26:40 ℹ <span class="o">[</span>INFO] <span class="nt">--------------------</span> <span class="o">[</span> Processing <span class="k">done</span><span class="nb">.</span> <span class="o">]</span> <span class="nt">--------------------</span> 2022-03-29 11:26:40 ✔ <span class="o">[</span>SUCCESS] All plugins applied successfully. 2022-03-29 11:26:40 ✔ <span class="o">[</span>SUCCESS] Apply finished. </code></pre> </div> <p>We can see that the file has drifted from the state, so it will be updated. After the operation, the file's content is changed back to what we defined in our config.</p> <p>Heck, let's even try deleting this file and see what happens:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span><span class="nb">rm </span>foo.txt <span class="nv">$ </span>./dtm <span class="nt">-y</span> apply <span class="nt">-f</span> config-localfile-test.yaml 2022-03-29 11:27:33 ℹ <span class="o">[</span>INFO] Apply started. 2022-03-29 11:27:33 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-03-29 11:27:33 ℹ <span class="o">[</span>INFO] Tool my-file <span class="o">(</span>localfile<span class="o">)</span> state found but it seems the tool isn<span class="s1">'t created, will be created. 2022-03-29 11:27:33 ℹ [INFO] Start executing the plan. 2022-03-29 11:27:33 ℹ [INFO] Changes count: 1. 2022-03-29 11:27:33 ℹ [INFO] -------------------- [ Processing progress: 1/1. ] -------------------- 2022-03-29 11:27:33 ℹ [INFO] Processing: my-file (localfile) -&gt; Create ... 2022-03-29 11:27:33 ✔ [SUCCESS] Plugin my-file(localfile) Create done. 2022-03-29 11:27:33 ℹ [INFO] -------------------- [ Processing done. ] -------------------- 2022-03-29 11:27:33 ✔ [SUCCESS] All plugins applied successfully. 2022-03-29 11:27:33 ✔ [SUCCESS] Apply finished. </span></code></pre> </div> <p>Apparently, DevStream thought the file should exist according to the state, but it doesn't, so it is created again.</p> <p>Last, let's try to <code>delete</code> this file with <code>dtm</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>./dtm <span class="nt">-y</span> delete <span class="nt">-f</span> config-localfile-test.yaml 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Delete started. 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Using <span class="nb">dir</span> &lt;.devstream&gt; to store plugins. 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Tool my-file <span class="o">(</span>localfile<span class="o">)</span> will be deleted. 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Start executing the plan. 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Changes count: 1. 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] <span class="nt">--------------------</span> <span class="o">[</span> Processing progress: 1/1. <span class="o">]</span> <span class="nt">--------------------</span> 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Processing: my-file <span class="o">(</span>localfile<span class="o">)</span> -&gt; Delete ... 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] Prepare to delete <span class="s1">'my-file_localfile'</span> from States. 2022-03-29 11:32:24 ✔ <span class="o">[</span>SUCCESS] Plugin my-file <span class="o">(</span>localfile<span class="o">)</span> delete <span class="k">done</span><span class="nb">.</span> 2022-03-29 11:32:24 ℹ <span class="o">[</span>INFO] <span class="nt">--------------------</span> <span class="o">[</span> Processing <span class="k">done</span><span class="nb">.</span> <span class="o">]</span> <span class="nt">--------------------</span> 2022-03-29 11:32:24 ✔ <span class="o">[</span>SUCCESS] All plugins deleted successfully. 2022-03-29 11:32:24 ✔ <span class="o">[</span>SUCCESS] Delete finished. </code></pre> </div> <p>Hooray!</p> <h2> Summary </h2> <p>I'm sorry that I put the "TL;DR" at the end; it's only because I want you to have a global feeling of DevStream. But for advanced developers, here's a "TL;DR" (or use it as a checklist) for you:</p> <ul> <li>Run <code>dtm develop create-plugin --name=your-plugin</code>.</li> <li>Do a <code>git status</code>, then: <ul> <li>find the generated files</li> <li>edit/code at the places where there is already a comment mark</li> </ul> </li> <li>Pay special attention to the return value of the interfaces. Figure out the relationship between <em>State</em> and <em>Resource</em>. What the interfaces return decides how this plugin will behave.</li> <li>Last but not least, update the documentation.</li> </ul> <p>If you enjoy reading this post, please like, comment, and subscribe! I will see you next time.</p> devops devstream go