wiki/challenges.suse-cloud-native-fundamentals-scholarship-program.html
2022-07-29 15:41:17 +00:00

586 lines
123 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width"/><meta charSet="utf-8"/><title>Solutions to SUSE Cloud native fundamentals scholarship exercises</title><script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script><script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script><script type="text/x-mathjax-config">
MathJax = {
tex: {
inlineMath: [ [&#x27;$&#x27;,&#x27;$&#x27;], [&#x27;\(&#x27;,&#x27;\)&#x27;] ],
displayMath: [ [&#x27;$$&#x27;,&#x27;$$&#x27;], [&#x27;[&#x27;,&#x27;]&#x27;] ]
},
options = {
processHtmlClass = &quot;math&quot;
}
}
</script><meta name="next-head-count" content="6"/><link rel="preload" href="/wiki/_next/static/css/52fc2ba29703df73922c.css" as="style"/><link rel="stylesheet" href="/wiki/_next/static/css/52fc2ba29703df73922c.css" data-n-g=""/><noscript data-n-css=""></noscript><link rel="preload" href="/wiki/_next/static/chunks/main-ae4733327bd95c4ac325.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/webpack-50bee04d1dc61f8adf5b.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/framework.9d524150d48315f49e80.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/commons.0e1c3f9aa780c2dfe9f0.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/pages/_app-8e3d0c58a60ec788aa69.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/940643274e605e7596ecea1f2ff8d83317a3fb76.4841a16762f602a59f00.js" as="script"/><link rel="preload" href="/wiki/_next/static/chunks/pages/%5B%5B...slug%5D%5D-1aa198f87ede1cd0e1dc.js" as="script"/></head><body><div id="__next"><main><h1>Solutions to SUSE Cloud native fundamentals scholarship exercises</h1><section class="post-metadata"><span>Date: <!-- -->2021-06-08 23:23:35 +08:00</span><span>Date modified: <!-- -->2021-07-07 16:33:05 +08:00</span></section><nav class="toc"><ol class="toc-level toc-level-1"><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#note-on-my-personal-setup" class="toc-link toc-link-h1">Note on my personal setup</a></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#trade-offs-for-monoliths-and-microservices" class="toc-link toc-link-h1">Trade-offs for monoliths and microservices</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution" class="toc-link toc-link-h2">Solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#endpoints-for-an-application-status" class="toc-link toc-link-h1">Endpoints for an application status</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-1" class="toc-link toc-link-h2">Solution</a></li><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution" class="toc-link toc-link-h2">Findings after solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#application-logging" class="toc-link toc-link-h1">Application logging</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-2" class="toc-link toc-link-h2">Solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#docker-for-application-packaging" class="toc-link toc-link-h1">Docker for application packaging</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#first-working-solution" class="toc-link toc-link-h2">First working solution</a></li><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-1" class="toc-link toc-link-h2">Findings after solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#deploy-your-first-kubernetes-cluster" class="toc-link toc-link-h1">Deploy your first Kubernetes cluster</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-3" class="toc-link toc-link-h2">Solution</a></li><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-2" class="toc-link toc-link-h2">Findings after solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#kubernetes-resources" class="toc-link toc-link-h1">Kubernetes resources</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-4" class="toc-link toc-link-h2">Solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#declarative-kubernetes-manifests" class="toc-link toc-link-h1">Declarative Kubernetes manifests</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-5" class="toc-link toc-link-h2">Solution</a></li><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-3" class="toc-link toc-link-h2">Findings after solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#continuous-application-deployment" class="toc-link toc-link-h1">Continuous application deployment</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-6" class="toc-link toc-link-h2">Solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#the-cd-fundamentals" class="toc-link toc-link-h1">The CD fundamentals</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-7" class="toc-link toc-link-h2">Solution</a></li></ol></li><li class="toc-item toc-item-h1"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#configuration-managers" class="toc-link toc-link-h1">Configuration managers</a><ol class="toc-level toc-level-2"><li class="toc-item toc-item-h2"><a href="/wiki/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-8" class="toc-link toc-link-h2">Solution</a></li></ol></li></ol></nav><p>I&#x27;ll attempt to archive my answers to exercise here in one Org mode document.
Let&#x27;s see the reproducibility capability of this thing.
</p><p>For future references, the lessons have their repo.
</p><ul><li><p><a href="https://github.com/udacity/nd064_course_1">Lesson 1 exercises</a></p></li></ul><h1 id="note-on-my-personal-setup">Note on my personal setup</h1><p>For this program, I&#x27;m using Podman instead of Docker.
There are subtle differences when using with Podman — the biggest difference being it is not attached to the Docker registry by default.
</p><table><thead><tr><th>Docker</th><th>Podman</th></tr></thead><tbody><tr><td><code class="inline-verbatim">docker pull alpine</code></td><td><code class="inline-verbatim">podman pull docker.io/alpine</code></td></tr><tr><td><code class="inline-verbatim">docker search alpine</code></td><td><code class="inline-verbatim">podman search docker.io/alpine</code></td></tr></tbody></table><p>Since Podman is not attached to the Docker registry by default, you also have to specify this in Dockerfiles.
</p><pre class="src-block"><code class="language-docker">FROM docker.io/alpine
</code></pre><p>You can make Podman search through DockerHub by setting it as one of the fallback registries.
Just set <code class="inline-verbatim">unqualified-search-registries</code> from your container configuration (see <code class="inline-verbatim">containers-registries.conf.5</code> manual page for more details).
</p><pre class="src-block"><code class="language-toml">unqualified-search-registries = [&#x27;docker.io&#x27;]
</code></pre><p>Don&#x27;t forget to login to DockerHub (e.g., <code class="inline-code">podman login docker.io</code>).
</p><h1 id="trade-offs-for-monoliths-and-microservices">Trade-offs for monoliths and microservices</h1><p>From the early stages of application development, it is fundamental to understand the requirements and available resources.
Overall, these will contour the architecture decisions.
</p><p>Imagine this scenario: you are part of the team that needs to outline the structure of a centralized system to book flight tickets for different airlines.
At this stage, the clients require the front-end(UI), payment, and customer functionalities to be designed.
Also, these are the individual requirements of each airline:
</p><ul><li><p>Airline A - payments should be allowed only through PayPal
</p></li><li><p>Airline B - payments should be disabled (bookings will be exclusively in person or via telephone)
</p></li><li><p>Airline C - payments should be allowed to use PayPal and debit cards
</p></li></ul><p>Using the above requirements, outline the application architecture.
Also, elaborate your reasoning on choosing a microservice or monolith based approach.
</p><h2 id="solution">Solution</h2><p>Considering that the airlines has an overlap of use cases particularly with Airline A and C both allowing Paypal, we&#x27;re leaning into considering a microservice architecture.
Each component in the service can then be configured individually by the development team of each airline.
We could also take in the factor if one of the airline changes its requirements, we would only have to inspect one component.
Having an monolith would be nice that all of the requirements of the airlines is wrapped in one package but if one team would have to maintain it, it would require them to go through the entire stack of each airline.
In this case, they would have to do it three times.
</p><p>In my opinion, microservices would be a better choice.
</p><p>Here&#x27;s the summarized outline of the application design we&#x27;re going to develop.
</p><ul><li><p>Each component of the application would have to be stored with their own repository.
</p></li><li><p>The front-end can be developed in parallel as we prioritize the payment system.
We can then improve it as we develop the other and ideally make it easy for the clients to modify it for their own needs.
</p></li><li><p>The payment system can then be configured to integrate with different services or be disabled entirely.
</p></li><li><p>Set up individual pipelines for each component as we can test them individually at different pace.
</p></li></ul><h1 id="endpoints-for-an-application-status">Endpoints for an application status</h1><p>This exercise can be located in the Lesson 1 exercise repo at <code class="inline-verbatim">exercises/python-helloworld</code>.
</p><p>Extend the Python Flask application with /status and /metrics endpoints, considering the following requirements:
</p><ul><li><p>Both endpoints should return an HTTP 200 status code
</p></li><li><p>Both endpoints should return a JSON response e.g. <code class="inline-verbatim">{&quot;user&quot;: &quot;admin&quot;}</code>. (Note: the JSON response can be hardcoded at this stage)
</p></li><li><p>The <code class="inline-verbatim">/status</code> endpoint should return a response similar to this example: <code class="inline-verbatim">result: OK - healthy</code></p></li><li><p>The <code class="inline-verbatim">/metrics</code> endpoint should return a response similar to this example: <code class="inline-verbatim">data: {UserCount: 140, UserCountActive: 23}</code></p></li></ul><h2 id="solution-1">Solution</h2><p>For the prerequisites, you can just install Flask and you&#x27;re mostly done.
For future references, here are the version of the tools I&#x27;ve used at the time.
</p><pre class="src-block"><code class="language-bash">nix-shell -p &#x27;python3Packages.flask&#x27; entr --run &#x27;flask --version&#x27;
</code></pre><pre class="fixed-width">Python 3.8.9
Flask 1.1.2
Werkzeug 1.0.1</pre><p>
As for the solution:
</p><pre class="src-block"><code class="language-python">from flask import Flask
app = Flask(__name__)
@app.route(&quot;/&quot;)
def hello():
return &quot;Hello World!&quot;
@app.route(&quot;/status&quot;)
def health_check():
return { &quot;result&quot;: &quot;OK - healthy&quot; }
@app.route(&quot;/metrics&quot;)
def metrics():
return { &quot;data&quot;: { &quot;UserCount&quot;: 140, &quot;UserCountActive&quot;: 23} }
if __name__ == &quot;__main__&quot;:
app.run(host=&#x27;0.0.0.0&#x27;)
</code></pre><h2 id="findings-after-solution">Findings after solution</h2><p>Comparing my solution from the solution shown in the video, I found out that Flask converts Python dictionaries into JSON.
We&#x27;re still good on that note.
</p><h1 id="application-logging">Application logging</h1><p>Logging is a core factor in increasing the visibility and transparency of an application.
When in troubleshooting or debugging scenarios, it is paramount to pin-point the functionality that impacted the service.
This exercise will focus on bringing the logging capabilities to an application.
</p><p>At this stage, you have extended the Hello World application to handle different endpoints.
Once an endpoint is reached, a log line should be recorded showcasing this operation.
In this exercise, you need to further develop the Hello World application collect logs, with the following requirements:
</p><ul><li><p>A log line should be recorded the timestamp and the requested endpoint e.g. <code class="inline-verbatim">&quot;{{TIMESTAMP}}, {{ ENDPOINT_NAME}} endpoint was reached&quot;</code>.
</p></li><li><p>The logs should be stored in a file with the name <code class="inline-verbatim">app.log</code>.
Refer to the logging Python module for more details.
</p></li><li><p>Enable the collection of Python logs at the DEBUG level.
Refer to the logging Python module for more details.
</p></li></ul><h2 id="solution-2">Solution</h2><p>Continuing from <a href="/wiki/Endpoints%20for%20an%20application%20status">Endpoints for an application status</a>, here is the resulting Python code.
</p><pre class="src-block"><code class="language-python">import logging
from flask import Flask
app = Flask(__name__)
@app.route(&quot;/&quot;)
def hello():
logging.info(&quot;/ endpoint was reached&quot;)
return &quot;Hello World!&quot;
@app.route(&quot;/status&quot;)
def health_check():
logging.info(&quot;/status endpoint was reached&quot;)
return { &quot;result&quot;: &quot;OK - healthy&quot; }
@app.route(&quot;/metrics&quot;)
def metrics():
logging.info(&quot;/metrics endpoint was reached&quot;)
return { &quot;data&quot;: { &quot;UserCount&quot;: 140, &quot;UserCountActive&quot;: 23} }
if __name__ == &quot;__main__&quot;:
logging.basicConfig(format=&quot;%(asctime)s, %(message)s&quot;, level=logging.DEBUG, filename=&quot;app.log&quot;)
app.run(host=&#x27;0.0.0.0&#x27;)
</code></pre><h1 id="docker-for-application-packaging">Docker for application packaging</h1><p>Create the Docker image for the Go web application and push it to DockerHub, considering the following requirements:
</p><p>Dockerfile:
</p><ul><li><p>use the <code class="inline-verbatim">golang:alpine</code> base image
</p></li><li><p>set the working directory to <code class="inline-verbatim">/go/src/app</code></p></li><li><p>make sure to copy all the files from the current directory to the container working directory (e.g. <code class="inline-verbatim">/go/src/app</code>)
</p></li><li><p>to build the application, use <code class="inline-verbatim">go build -o helloworld</code> command, where <code class="inline-verbatim">-o helloworld</code> will create the binary of the application with the name <code class="inline-verbatim">helloworld</code></p></li><li><p>the application should be accessible on port <code class="inline-verbatim">6111</code></p></li><li><p>and lastly, the command to start the container is to invoke the binary created earlier, which is <code class="inline-verbatim">./helloworld</code></p></li></ul><p>Docker image:
</p><ul><li><p>should have the name <code class="inline-verbatim">go-helloworld</code></p></li><li><p>should have a valid tag, and a version with a major, minor, and patch included
</p></li><li><p>should be available in DockerHub, under your username e.g. <code class="inline-verbatim">pixelpotato/go-helloworld</code></p></li></ul><p>Docker container:
</p><ul><li><p>should be running on your local machine, by referencing the image from the DockerHub with a valid tag e.g. <code class="inline-verbatim">pixelpotato/go-helloworld:v5.12.3</code></p></li></ul><h2 id="first-working-solution">First working solution</h2><p>This should be simple enough as we can see from the file structure.
</p><pre class="src-block"><code>go-helloworld
├── main.go
└── README.md
</code></pre><p>As for the Dockerfile, I&#x27;ve made the following:
</p><pre class="src-block"><code class="language-docker">FROM docker.io/golang:alpine
COPY . /go/src/app
WORKDIR /go/src/app
RUN go build -o helloworld main.go
CMD [&quot;./helloworld&quot;]
</code></pre><p>As for the requirements of the images and running the containerized app, we&#x27;ll summarize it with the following Bash script.
</p><pre class="src-block"><code class="language-bash">OWNER=&quot;foodogsquared&quot;
IMG=&quot;go-helloworld&quot;
VERSION=&quot;1.0.0&quot;
REMOTE_IMG=&quot;${OWNER}/${IMG}:v${VERSION}&quot;
# Build the image with the tag already in place.
podman build --tag &quot;$IMG&quot; .
# Run the packaged app.
podman run -d -p 6111:6111 &quot;$IMG&quot;
# Verify it&#x27;s running.
podman ps
# Create another image to push it into the Docker registry with the proper naming.
podman tag &quot;$IMG&quot; &quot;$REMOTE_IMG&quot;
# Push the image to the Docker registry.
podman push &quot;$REMOTE_IMG&quot;
</code></pre><h2 id="findings-after-solution-1">Findings after solution</h2><p>I guess my solution is close enough, I didn&#x27;t realize the application should be configured its port to be exposed already in the Dockerfile and not when running the containerized app.
Whoops!
</p><p>Apparently, there is the <a href="https://docs.docker.com/engine/reference/builder/#expose"><code class="inline-verbatim">EXPOSE</code></a> instruction, just requiring a port number.
I also tested the Dockerfile from the solution and it still gave me an error from build time.
</p><p>I also didn&#x27;t realize the solution is pretty much how the instructions laid it out.
At the end, it should look like the following code.
</p><pre class="src-block"><code class="language-docker">FROM docker.io/golang:alpine
WORKDIR /go/src/app
ADD . .
RUN go build -o helloworld main.go
EXPOSE 6111
CMD [&quot;./helloworld&quot;]
</code></pre><h1 id="deploy-your-first-kubernetes-cluster">Deploy your first Kubernetes cluster</h1><p>Now you should have a Kubernetes cluster up and running.
Examine the cluster and identity of the following details.
</p><p>From the kubeconfig, identify:
</p><ul><li><p>the IP and port of the API server
</p></li><li><p>authentication mechanism
</p></li></ul><p>From the cluster using kubectl commands to identify:
</p><ul><li><p>endpoints of the control plane and add-ons
</p></li><li><p>amount of nodes
</p></li><li><p>node internal IP
</p></li><li><p>the pod CIDR allocate to the node
</p></li></ul><h2 id="solution-3">Solution</h2><p>From my setup with the given Vagrantfile from the lesson repo — i.e., after installing k3s in the virtual machine — you can inspect the kubeconfig located at <code class="inline-verbatim">/etc/rancher/k3s/k3s.yaml</code>.
</p><p>The IP and the port of the API server is visible from there.
In my case, it is 127.0.0.1 at port 6443.
</p><p>As for the authentication mechanism, I&#x27;m not sure.
Both the cluster and the user have an attached certificate data.
It seems to be using matching certificate data from the user and the cluster.
</p><p>As for getting cluster-related information.
</p><ul><li><p>Getting the endpoints of the control plane and the add-ons through <code class="inline-code">kubectl cluster-info</code>.
</p></li><li><p>One way of getting the amount of nodes is through <code class="inline-code">kubectl get nodes</code> where it will print the nodes and their information one line at a time.
</p></li><li><p>For the node&#x27;s internal IP and the pod CIDR, both of them can be extracted with <code class="inline-code">kubectl describe node ${NODE_NAME}</code>.
</p></li></ul><h2 id="findings-after-solution-2">Findings after solution</h2><p>I mostly got it right.
It turns out there are <a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/">different methods for authentication</a>.
While the solution gave it as user and passwords, the kubeconfig I have seem to be using keys and certificates. </p><p>Also, you can get the configuration of the cluster with <code class="inline-code">kubectl config view</code>.
Pretty handy.
</p><h1 id="kubernetes-resources">Kubernetes resources</h1><p>Now you have learned many Kubernetes recourses, in this exercise, you will deploy the following resources using the kubectl command.
</p><ul><li><p>a namespace
</p><ul><li><p>name: <code class="inline-verbatim">demo</code></p></li><li><p>label: <code class="inline-verbatim">tier: test</code></p></li></ul></li><li><p>a deployment:
</p><ul><li><p>image: <code class="inline-verbatim">nginx:alpine</code></p></li><li><p>name: <code class="inline-verbatim">nginx-apline</code></p></li><li><p>namespace: <code class="inline-verbatim">demo</code></p></li><li><p>replicas: <code class="inline-verbatim">3</code></p></li><li><p>labels: <code class="inline-verbatim">app: nginx, tag: alpine</code></p></li></ul></li><li><p>a service:
</p><ul><li><p>expose the above deployment on port 8111
</p></li><li><p>namespace: <code class="inline-verbatim">demo</code></p></li></ul></li><li><p>a configmap:
</p><ul><li><p>name: <code class="inline-verbatim">nginx-version</code></p></li><li><p>containing key-value pair: <code class="inline-verbatim">version=alpine</code></p></li><li><p>namespace: <code class="inline-verbatim">demo</code></p></li></ul></li></ul><h2 id="solution-4">Solution</h2><p>This is practical test but it can summarized with a shell script.
</p><pre class="src-block"><code class="language-bash"># Create the namespace with the specified label.
kubectl create namespaces demo
kubectl label namespaces demo tier=test
# Create the specified deployment.
kubectl create deployment nginx-alpine --image=nginx:alpine --replicas=3 --namespace=demo
kubectl label deployment nginx-alpine app=nginx tag=alpine --namespace=demo
# Expose the deployment as a service.
kubectl expose deployment nginx-alpine --namespace=demo --port=8111
# Create the config map.
kubectl create configmaps nginx-version --namespace=demo --from-literal=version=alpine
</code></pre><h1 id="declarative-kubernetes-manifests">Declarative Kubernetes manifests</h1><p>Kubernetes is widely known for its imperative and declarative management techniques.
In the previous exercise, you have deployed the following resources using the imperative approach.
Now deploy them using the declarative approach.
</p><ul><li><p>a namespace
</p><ul><li><p>name: demo
</p></li><li><p>label: tier: test
</p></li></ul></li><li><p>a deployment:
</p><ul><li><p>image: nginx:alpine
</p></li><li><p>name:nginx-apline
</p></li><li><p>namespace: demo
</p></li><li><p>replicas: 3
</p></li><li><p>labels: app: nginx, tag: alpine
</p></li></ul></li><li><p>a service:
</p><ul><li><p>expose the above deployment on port 8111
</p></li><li><p>namespace: demo
</p></li></ul></li><li><p>a configmap:
</p><ul><li><p>name: nginx-version
</p></li><li><p>containing key-value pair: version=alpine
</p></li><li><p>namespace: demo
</p></li></ul></li></ul><h2 id="solution-5">Solution</h2><p>Since they ask for 4 resources, we need 4 manifests.
We&#x27;ll create four YAML manifests for this exercise.
</p><p>The following manifests are created with the option to print the resources in YAML format with some modifications.
</p><p>Here&#x27;s one for the namespace.
The command used to make the template is <code class="inline-code">kubectl create namespace demo --dry-run=client --output=yaml</code>.
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: demo
labels:
tier: test
spec: {}
status: {}
</code></pre><p>The manifest for the deployment.
The command used to create the starting template is <code class="inline-code">kubectl create deployment nginx-alpine --namespace=demo --replicas=3 --image=nginx:alpine --dry-run=client --output=yaml</code>.
</p><pre class="src-block"><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
tag: alpine
name: nginx-alpine
namespace: demo
spec:
replicas: 3
selector:
matchLabels:
app: nginx-alpine
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx-alpine
spec:
containers:
- image: nginx:alpine
name: nginx
resources: {}
status: {}
</code></pre><p>The service manifest should be created after the deployment manifest is applied (i.e., <code class="inline-code">kubectl apply -f deployment.yaml</code>).
It is created with <code class="inline-code">kubectl expose deploy nginx-alpine --port=8111 --dry-run=client --output=yaml --namespace=demo</code>.
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
tag: alpine
name: nginx-alpine
namespace: demo
spec:
ports:
- port: 8111
protocol: TCP
targetPort: 8111
selector:
app: nginx-alpine
status:
loadBalancer: {}
</code></pre><p>The resulting YAML output is from running the command (i.e., <code class="inline-code">kubectl create configmap nginx-version --from-literal=version=alpine --dry-run=client --output=yaml --namespace=demo</code>).
No cleaning up is required.
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
data:
version: alpine
kind: ConfigMap
metadata:
creationTimestamp: null
name: nginx-version
namespace: demo
</code></pre><h2 id="findings-after-solution-3">Findings after solution</h2><p>Aside from the mostly correct answers, I also found out <code class="inline-code">kubectl get all -n demo</code> to get all of the resources in the specified namespace.
Pretty handy for inspecting application-specific resources.
</p><h1 id="continuous-application-deployment">Continuous application deployment</h1><p>Create a new GitHub Actions in the <code class="inline-verbatim">/.github/workflows/docker-build.yml</code> that will build and push the Docker image for a Python web application, with the following requirements:
</p><ul><li><p>Image name: <code class="inline-verbatim">python-helloworld</code></p></li><li><p>Tag: <code class="inline-verbatim">latest</code></p></li><li><p>Platforms: <code class="inline-verbatim">platforms: linux/amd64,linux/arm64</code></p></li></ul><p>GitHub marketplace has a rich suite of upstream actions that can be easily integrated within a repository.
One of the upstream action is <a href="https://github.com/marketplace/actions/build-and-push-docker-images">Build and Push Docker images</a>, which can be used to implement the required CI task.
</p><p>The above GitHub action uses DockerHub Tokens and encrypted GitHub secrets to login into DockerHub and to push new images.
To set up these credentials refer to the following resources:
</p><ul><li><p>Create <a href="https://www.docker.com/blog/docker-hub-new-personal-access-tokens/">DockerHub Tokens</a></p></li><li><p>Create <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets">GitHub encrypted secrets</a></p></li></ul><h2 id="solution-6">Solution</h2><p>After creating a DockerHub access token (that serves as an alternative to passwords) and creating a GitHub encrypted secret, the workflow should now work.
</p><p>Here&#x27;s the resulting GitHub Actions workflow file:
</p><pre class="src-block"><code class="language-yaml">name: Docker build image
on: [push]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/python-helloworld:latest
platforms: linux/amd64,linux/arm64
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
</code></pre><p>This is shamelessly ripped off from the sample from the <a href="https://github.com/marketplace/actions/build-and-push-docker-images">GitHub Actions page</a>.
I realized it&#x27;s basically the answer for this exercise.
Embarrassing that I spent an hour for this.
</p><h1 id="the-cd-fundamentals">The CD fundamentals</h1><p>Continuous Delivery (CD) is the ability to get code changes reliably to production environments.
This practice should be automated and should enable developers to provide value to consumers efficiently.
</p><p>In this exercise, you will use ArgoCD to automate the delivery of an application to a Kubernetes cluster.
</p><h2 id="solution-7">Solution</h2><p>The manifest is really the same as the example manifest from the walkthrough except with a different path pointing to the required manifests.
</p><pre class="src-block"><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ngnix-alpine
namespace: argocd
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
path: exercises/manifests
repoURL: https://github.com/udacity/nd064_course_1
targetRevision: HEAD
syncPolicy: {}
</code></pre><p>Then integrate it into the cluster with the following command.
</p><pre class="src-block"><code class="language-shell">kubectl apply -f argocd-nginx-alpine.yaml
</code></pre><p>You should then see the project on the ArgoCD application list where it requires an initial sync.
</p><p>For assurance, you can check to see if the resources are deployed.
Continuing from <a href="/wiki/Declarative%20Kubernetes%20manifests">Declarative Kubernetes manifests</a>, the resources are mostly in the <code class="inline-verbatim">demo</code> namespace.
</p><pre class="src-block"><code class="language-shell">kubectl get pod -n demo
kubectl get deploy -n demo
kubectl get rs -n demo
</code></pre><p>You should see them up and running.
</p><h1 id="configuration-managers">Configuration managers</h1><p>Using the manifests provided in the course repository, create a helm chart (Chart.yaml, templates, values.yaml) that will template the following parameters:
</p><ul><li><p>namespace name
</p></li><li><p>replica count
</p></li><li><p>image:
</p><ul><li><p>name
</p></li><li><p>tag
</p></li><li><p>pull policy
</p></li></ul></li><li><p>resources
</p><ul><li><p>requests for CPU and memory
</p></li></ul></li><li><p>service
</p><ul><li><p>port
</p></li><li><p>type (e.g. ClusterIP)
</p></li></ul></li><li><p>configmap data (e.g. the key-value pair)
</p></li></ul><p>The chart details should be as following:
</p><ul><li><p>name: nginx-deployment
</p></li><li><p>version: 1.0.0
</p></li><li><p>keywords: nginx
</p></li></ul><p>Once the Helm chart is available make sure that a default values.yaml file is available. This values file will be used as a default input file for the Helm chart. The values.yaml file should have the following specification:
</p><ul><li><p>values.yaml
</p><ul><li><p>namespace name: demo
</p></li><li><p>replica count: 3
</p></li><li><p>image repository: nginx
</p></li><li><p>image tag: alpine
</p></li><li><p>image pull policy: IfNotPresent
</p></li><li><p>resources: CPU 50m and memory 256Mi
</p></li><li><p>service type: ClusterIP
</p></li><li><p>service port: 8111
</p></li><li><p>configmap data: &quot;version: alpine&quot;
</p></li></ul></li></ul><p>Next, create 2 values files with the following specifications:
</p><ul><li><p>values-staging.yaml
</p><ul><li><p>namespace name: staging
</p></li><li><p>replica count: 1
</p></li><li><p>image repository: nginx
</p></li><li><p>image tag: 1.18.0
</p></li><li><p>resources: CPU 50m and memory 128Mi
</p></li><li><p>configmap data: &quot;version: 1.18.0&quot;
</p></li></ul></li><li><p>values-prod.yaml
</p><ul><li><p>namespace name: prod
</p></li><li><p>replica count: 2
</p></li><li><p>image repository: nginx
</p></li><li><p>image tag: 1.17.0
</p></li><li><p>resources: CPU 70m and memory 256Mi
</p></li><li><p>service port: 80
</p></li><li><p>configmap data: &quot;version: 1.17.0&quot;
</p></li></ul></li></ul><p>Finally, using the values files above (values-prod, values-staging), create 2 ArgoCD application, nginx-staging and nginx-prod respectively. These should deploy the nginx Helm Chart referencing each input values files.
</p><h2 id="solution-8">Solution</h2><p>With the given manifests, we&#x27;ll have to create a Helm package (or a Chart).
Here&#x27;s what the file structure of the chart.
</p><pre class="src-block"><code>helm-nginx
├── templates
│   ├── configmap.yaml
│   ├── deployment.yaml
│   ├── namespace.yaml
│   └── service.yaml
├── Chart.yaml
├── values.yaml
├── values-prod.yaml
└── values-staging.yaml
</code></pre><p>The chart definition is the following file.
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
name: nginx-deployment
version: 1.0.0
keywords:
- nginx
</code></pre><p>With the chart definition, we move on to the values to be used in the templates.
It&#x27;s up to you how to structure the data but here&#x27;s my solution on it.
</p><pre class="src-block"><code class="language-yaml"># namespace refers to the Kubernetes namespace resource.
namespace:
name: demo
# replicaCount is the number of instances to run in a replica set.
replicaCount: 3
# image contains the detail of the container image to be used
image:
repository: nginx
tag: alpine
pullPolicy: IfNotPresent
# resources dictate the amount to spend
resources:
cpu: 50m
memory: 256Mi
# service configures the Kubernetes service resource
service:
type: ClusterIP
port: 8111
# configmap configures the Kubernetes configmap resource
configmap:
data: &quot;version: alpine&quot;
</code></pre><p>As for different versions of the values such as...
</p><p>...for development version (<code class="inline-verbatim">values-staging.yaml</code>)...
</p><pre class="src-block"><code class="language-yaml">namespace:
name: staging
replicaCount: 1
image:
repository: nginx
tag: 1.18.0
resources:
cpu: 50m
memory: 128Mi
configmap:
data: &quot;version: 1.18.0&quot;
</code></pre><p>...and for production (<code class="inline-verbatim">values-prod.yaml</code>).
</p><pre class="src-block"><code class="language-yaml">namespace:
name: prod
replicaCount: 2
image:
repository: nginx
tag: 1.17.0
resources:
cpu: 70m
memory: 256Mi
configmap:
data: &quot;version: 1.17.0&quot;
</code></pre><p>With the templates, it is already given to us from the course exercise repo.
We just have to parameterize some of the values from the value file.
</p><p>Here&#x27;s one for the configmap...
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
data:
{{ .Values.configmap.data }}
kind: ConfigMap
metadata:
name: nginx-version
namespace: {{ .Values.namespace.name }}
</code></pre><p>..., deployment...
</p><pre class="src-block"><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
tag: alpine
name: nginx-alpine
namespace: {{ .Values.namespace.name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: nginx
tag: alpine
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: nginx
tag: alpine
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
name: nginx-alpine
</code></pre><p>..., namespace...
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
kind: Namespace
metadata:
labels:
tier: test
name: {{ .Values.namespace.name }}
</code></pre><p>..., and service.
</p><pre class="src-block"><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
tag: alpine
name: nginx-alpine
namespace: {{ .Values.namespace.name }}
spec:
ports:
- port: {{ .Values.service.port }}
protocol: TCP
targetPort: {{ .Values.service.port }}
selector:
app: nginx
tag: alpine
type: {{ .Values.service.type }}
</code></pre><p>To deploy it in ArgoCD, we&#x27;ll just have to specify it in the ArgoCD manifest.
Since ArgoCD is heavily a GitOps tool, we have to put the files in a Git repo.
For now, let&#x27;s assume the course repo as ours. <!-- -->
Just change the Git repo URL and the path if you have your own version.
</p><p>Additionally, the exercise requires to deploy two ArgoCD applications: one for staging (i.e., <code class="inline-verbatim">nginx-staging</code>) and production version (i.e., <code class="inline-verbatim">nginx-prod</code>) of the app.
</p><p>Here&#x27;s one for the production version...
</p><pre class="src-block"><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-prod
namespace: argocd
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
helm:
valueFiles:
- values-prod.yaml
path: structured/assets/challenges.suse-cloud-native-fundamentals-scholarship-program/helm-nginx
repoURL: https://github.com/foo-dogsquared/wiki
targetRevision: HEAD
syncPolicy: {}
</code></pre><p>...and the development version.
</p><pre class="src-block"><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-staging
namespace: argocd
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
helm:
valueFiles:
- values-staging.yaml
path: structured/assets/challenges.suse-cloud-native-fundamentals-scholarship-program/helm-nginx
repoURL: https://github.com/foo-dogsquared/wiki
targetRevision: HEAD
syncPolicy: {}
</code></pre><section><h2>Backlinks</h2><ul><li><a href="/wiki/literature.suse-cloud-native-fundamentals-scholarship-program">SUSE Cloud native fundamentals scholarship program</a></li></ul></section></main></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"metadata":{"date":"\"2021-06-08 23:23:35 +08:00\"","date_modified":"\"2021-07-07 16:33:05 +08:00\"","language":"en","source":""},"title":"Solutions to SUSE Cloud native fundamentals scholarship exercises","hast":{"type":"root","children":[{"type":"element","tagName":"nav","properties":{"className":"toc"},"children":[{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-1"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"note-on-my-personal-setup"},"children":[{"type":"text","value":"Note on my personal setup"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#note-on-my-personal-setup"},"children":[{"type":"text","value":"Note on my personal setup"}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"trade-offs-for-monoliths-and-microservices"},"children":[{"type":"text","value":"Trade-offs for monoliths and microservices"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#trade-offs-for-monoliths-and-microservices"},"children":[{"type":"text","value":"Trade-offs for monoliths and microservices"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution"},"children":[{"type":"text","value":"Solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"endpoints-for-an-application-status"},"children":[{"type":"text","value":"Endpoints for an application status"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#endpoints-for-an-application-status"},"children":[{"type":"text","value":"Endpoints for an application status"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-1"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-1"},"children":[{"type":"text","value":"Solution"}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution"},"children":[{"type":"text","value":"Findings after solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution"},"children":[{"type":"text","value":"Findings after solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"application-logging"},"children":[{"type":"text","value":"Application logging"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#application-logging"},"children":[{"type":"text","value":"Application logging"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-2"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-2"},"children":[{"type":"text","value":"Solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"docker-for-application-packaging"},"children":[{"type":"text","value":"Docker for application packaging"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#docker-for-application-packaging"},"children":[{"type":"text","value":"Docker for application packaging"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"first-working-solution"},"children":[{"type":"text","value":"First working solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#first-working-solution"},"children":[{"type":"text","value":"First working solution"}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-1"},"children":[{"type":"text","value":"Findings after solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-1"},"children":[{"type":"text","value":"Findings after solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"deploy-your-first-kubernetes-cluster"},"children":[{"type":"text","value":"Deploy your first Kubernetes cluster"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#deploy-your-first-kubernetes-cluster"},"children":[{"type":"text","value":"Deploy your first Kubernetes cluster"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-3"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-3"},"children":[{"type":"text","value":"Solution"}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-2"},"children":[{"type":"text","value":"Findings after solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-2"},"children":[{"type":"text","value":"Findings after solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"kubernetes-resources"},"children":[{"type":"text","value":"Kubernetes resources"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#kubernetes-resources"},"children":[{"type":"text","value":"Kubernetes resources"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-4"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-4"},"children":[{"type":"text","value":"Solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"declarative-kubernetes-manifests"},"children":[{"type":"text","value":"Declarative Kubernetes manifests"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#declarative-kubernetes-manifests"},"children":[{"type":"text","value":"Declarative Kubernetes manifests"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-5"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-5"},"children":[{"type":"text","value":"Solution"}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-3"},"children":[{"type":"text","value":"Findings after solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#findings-after-solution-3"},"children":[{"type":"text","value":"Findings after solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"continuous-application-deployment"},"children":[{"type":"text","value":"Continuous application deployment"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#continuous-application-deployment"},"children":[{"type":"text","value":"Continuous application deployment"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-6"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-6"},"children":[{"type":"text","value":"Solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"the-cd-fundamentals"},"children":[{"type":"text","value":"The CD fundamentals"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#the-cd-fundamentals"},"children":[{"type":"text","value":"The CD fundamentals"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-7"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-7"},"children":[{"type":"text","value":"Solution"}]}]}]}]},{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h1","properties":{"id":"configuration-managers"},"children":[{"type":"text","value":"Configuration managers"}]}]},"properties":{"className":"toc-item toc-item-h1"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h1","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#configuration-managers"},"children":[{"type":"text","value":"Configuration managers"}]},{"type":"element","tagName":"ol","properties":{"className":"toc-level toc-level-2"},"children":[{"type":"element","tagName":"li","data":{"hookArgs":[{"type":"element","tagName":"h2","properties":{"id":"solution-8"},"children":[{"type":"text","value":"Solution"}]}]},"properties":{"className":"toc-item toc-item-h2"},"children":[{"type":"element","tagName":"a","properties":{"className":"toc-link toc-link-h2","href":"/challenges.suse-cloud-native-fundamentals-scholarship-program#solution-8"},"children":[{"type":"text","value":"Solution"}]}]}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"I'll attempt to archive my answers to exercise here in one Org mode document.\nLet's see the reproducibility capability of this thing.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For future references, the lessons have their repo.\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"href":"https://github.com/udacity/nd064_course_1"},"children":[{"type":"text","value":"Lesson 1 exercises"}]}]}]}]},{"type":"element","tagName":"h1","properties":{"id":"note-on-my-personal-setup"},"children":[{"type":"text","value":"Note on my personal setup"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For this program, I'm using Podman instead of Docker.\nThere are subtle differences when using with Podman — the biggest difference being it is not attached to the Docker registry by default.\n"}]},{"type":"element","tagName":"table","properties":{},"children":[{"type":"element","tagName":"thead","properties":{},"children":[{"type":"element","tagName":"tr","properties":{},"children":[{"type":"element","tagName":"th","properties":{},"children":[{"type":"text","value":"Docker"}]},{"type":"element","tagName":"th","properties":{},"children":[{"type":"text","value":"Podman"}]}]}]},{"type":"element","tagName":"tbody","properties":{},"children":[{"type":"element","tagName":"tr","properties":{},"children":[{"type":"element","tagName":"td","properties":{},"children":[{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"docker pull alpine"}]}]},{"type":"element","tagName":"td","properties":{},"children":[{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"podman pull docker.io/alpine"}]}]}]},{"type":"element","tagName":"tr","properties":{},"children":[{"type":"element","tagName":"td","properties":{},"children":[{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"docker search alpine"}]}]},{"type":"element","tagName":"td","properties":{},"children":[{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"podman search docker.io/alpine"}]}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Since Podman is not attached to the Docker registry by default, you also have to specify this in Dockerfiles.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-docker"]},"children":[{"type":"text","value":"FROM docker.io/alpine\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"You can make Podman search through DockerHub by setting it as one of the fallback registries.\nJust set "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"unqualified-search-registries"}]},{"type":"text","value":" from your container configuration (see "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"containers-registries.conf.5"}]},{"type":"text","value":" manual page for more details).\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-toml"]},"children":[{"type":"text","value":"unqualified-search-registries = ['docker.io']\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Don't forget to login to DockerHub (e.g., "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"podman login docker.io"}]},{"type":"text","value":").\n"}]},{"type":"element","tagName":"h1","properties":{"id":"trade-offs-for-monoliths-and-microservices"},"children":[{"type":"text","value":"Trade-offs for monoliths and microservices"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"From the early stages of application development, it is fundamental to understand the requirements and available resources.\nOverall, these will contour the architecture decisions.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Imagine this scenario: you are part of the team that needs to outline the structure of a centralized system to book flight tickets for different airlines.\nAt this stage, the clients require the front-end(UI), payment, and customer functionalities to be designed.\nAlso, these are the individual requirements of each airline:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Airline A - payments should be allowed only through PayPal\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Airline B - payments should be disabled (bookings will be exclusively in person or via telephone)\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Airline C - payments should be allowed to use PayPal and debit cards\n"}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Using the above requirements, outline the application architecture.\nAlso, elaborate your reasoning on choosing a microservice or monolith based approach.\n"}]},{"type":"element","tagName":"h2","properties":{"id":"solution"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Considering that the airlines has an overlap of use cases particularly with Airline A and C both allowing Paypal, we're leaning into considering a microservice architecture.\nEach component in the service can then be configured individually by the development team of each airline.\nWe could also take in the factor if one of the airline changes its requirements, we would only have to inspect one component.\nHaving an monolith would be nice that all of the requirements of the airlines is wrapped in one package but if one team would have to maintain it, it would require them to go through the entire stack of each airline.\nIn this case, they would have to do it three times.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"In my opinion, microservices would be a better choice.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here's the summarized outline of the application design we're going to develop.\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Each component of the application would have to be stored with their own repository.\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The front-end can be developed in parallel as we prioritize the payment system.\n We can then improve it as we develop the other and ideally make it easy for the clients to modify it for their own needs.\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The payment system can then be configured to integrate with different services or be disabled entirely.\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Set up individual pipelines for each component as we can test them individually at different pace.\n"}]}]}]},{"type":"element","tagName":"h1","properties":{"id":"endpoints-for-an-application-status"},"children":[{"type":"text","value":"Endpoints for an application status"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This exercise can be located in the Lesson 1 exercise repo at "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"exercises/python-helloworld"}]},{"type":"text","value":".\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Extend the Python Flask application with /status and /metrics endpoints, considering the following requirements:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Both endpoints should return an HTTP 200 status code\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Both endpoints should return a JSON response e.g. "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"{\"user\": \"admin\"}"}]},{"type":"text","value":". (Note: the JSON response can be hardcoded at this stage)\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/status"}]},{"type":"text","value":" endpoint should return a response similar to this example: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"result: OK - healthy"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/metrics"}]},{"type":"text","value":" endpoint should return a response similar to this example: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"data: {UserCount: 140, UserCountActive: 23}"}]}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-1"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For the prerequisites, you can just install Flask and you're mostly done.\nFor future references, here are the version of the tools I've used at the time.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-bash"]},"children":[{"type":"text","value":"nix-shell -p 'python3Packages.flask' entr --run 'flask --version'\n"}]}]},{"type":"element","tagName":"pre","properties":{"className":["fixed-width"]},"children":[{"type":"text","value":"Python 3.8.9\nFlask 1.1.2\nWerkzeug 1.0.1"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"\nAs for the solution:\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-python"]},"children":[{"type":"text","value":"from flask import Flask\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef hello():\n return \"Hello World!\"\n\n@app.route(\"/status\")\ndef health_check():\n return { \"result\": \"OK - healthy\" }\n\n@app.route(\"/metrics\")\ndef metrics():\n return { \"data\": { \"UserCount\": 140, \"UserCountActive\": 23} }\n\nif __name__ == \"__main__\":\n app.run(host='0.0.0.0')\n"}]}]},{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution"},"children":[{"type":"text","value":"Findings after solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Comparing my solution from the solution shown in the video, I found out that Flask converts Python dictionaries into JSON.\nWe're still good on that note.\n"}]},{"type":"element","tagName":"h1","properties":{"id":"application-logging"},"children":[{"type":"text","value":"Application logging"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Logging is a core factor in increasing the visibility and transparency of an application.\nWhen in troubleshooting or debugging scenarios, it is paramount to pin-point the functionality that impacted the service.\nThis exercise will focus on bringing the logging capabilities to an application.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"At this stage, you have extended the Hello World application to handle different endpoints.\nOnce an endpoint is reached, a log line should be recorded showcasing this operation.\nIn this exercise, you need to further develop the Hello World application collect logs, with the following requirements:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"A log line should be recorded the timestamp and the requested endpoint e.g. "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"\"{{TIMESTAMP}}, {{ ENDPOINT_NAME}} endpoint was reached\""}]},{"type":"text","value":".\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The logs should be stored in a file with the name "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"app.log"}]},{"type":"text","value":".\n Refer to the logging Python module for more details.\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Enable the collection of Python logs at the DEBUG level.\n Refer to the logging Python module for more details.\n"}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-2"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Continuing from "},{"type":"element","tagName":"a","properties":{"href":"/Endpoints%20for%20an%20application%20status"},"children":[{"type":"text","value":"Endpoints for an application status"}]},{"type":"text","value":", here is the resulting Python code.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-python"]},"children":[{"type":"text","value":"import logging\nfrom flask import Flask\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef hello():\n logging.info(\"/ endpoint was reached\")\n return \"Hello World!\"\n\n@app.route(\"/status\")\ndef health_check():\n logging.info(\"/status endpoint was reached\")\n return { \"result\": \"OK - healthy\" }\n\n@app.route(\"/metrics\")\ndef metrics():\n logging.info(\"/metrics endpoint was reached\")\n return { \"data\": { \"UserCount\": 140, \"UserCountActive\": 23} }\n\nif __name__ == \"__main__\":\n logging.basicConfig(format=\"%(asctime)s, %(message)s\", level=logging.DEBUG, filename=\"app.log\")\n app.run(host='0.0.0.0')\n"}]}]},{"type":"element","tagName":"h1","properties":{"id":"docker-for-application-packaging"},"children":[{"type":"text","value":"Docker for application packaging"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Create the Docker image for the Go web application and push it to DockerHub, considering the following requirements:\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Dockerfile:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"use the "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"golang:alpine"}]},{"type":"text","value":" base image\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"set the working directory to "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/go/src/app"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"make sure to copy all the files from the current directory to the container working directory (e.g. "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/go/src/app"}]},{"type":"text","value":")\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"to build the application, use "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"go build -o helloworld"}]},{"type":"text","value":" command, where "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"-o helloworld"}]},{"type":"text","value":" will create the binary of the application with the name "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"helloworld"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"the application should be accessible on port "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"6111"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"and lastly, the command to start the container is to invoke the binary created earlier, which is "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"./helloworld"}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Docker image:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"should have the name "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"go-helloworld"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"should have a valid tag, and a version with a major, minor, and patch included\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"should be available in DockerHub, under your username e.g. "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"pixelpotato/go-helloworld"}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Docker container:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"should be running on your local machine, by referencing the image from the DockerHub with a valid tag e.g. "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"pixelpotato/go-helloworld:v5.12.3"}]}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"first-working-solution"},"children":[{"type":"text","value":"First working solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This should be simple enough as we can see from the file structure.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{},"children":[{"type":"text","value":"go-helloworld\n├── main.go\n└── README.md\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As for the Dockerfile, I've made the following:\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-docker"]},"children":[{"type":"text","value":"FROM docker.io/golang:alpine\n\nCOPY . /go/src/app\nWORKDIR /go/src/app\nRUN go build -o helloworld main.go\nCMD [\"./helloworld\"]\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As for the requirements of the images and running the containerized app, we'll summarize it with the following Bash script.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-bash"]},"children":[{"type":"text","value":"OWNER=\"foodogsquared\"\nIMG=\"go-helloworld\"\nVERSION=\"1.0.0\"\nREMOTE_IMG=\"${OWNER}/${IMG}:v${VERSION}\"\n\n# Build the image with the tag already in place.\npodman build --tag \"$IMG\" .\n\n# Run the packaged app.\npodman run -d -p 6111:6111 \"$IMG\"\n\n# Verify it's running.\npodman ps\n\n# Create another image to push it into the Docker registry with the proper naming.\npodman tag \"$IMG\" \"$REMOTE_IMG\"\n\n# Push the image to the Docker registry.\npodman push \"$REMOTE_IMG\"\n"}]}]},{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-1"},"children":[{"type":"text","value":"Findings after solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"I guess my solution is close enough, I didn't realize the application should be configured its port to be exposed already in the Dockerfile and not when running the containerized app.\nWhoops!\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Apparently, there is the "},{"type":"element","tagName":"a","properties":{"href":"https://docs.docker.com/engine/reference/builder/#expose"},"children":[{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"EXPOSE"}]}]},{"type":"text","value":" instruction, just requiring a port number.\nI also tested the Dockerfile from the solution and it still gave me an error from build time.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"I also didn't realize the solution is pretty much how the instructions laid it out.\nAt the end, it should look like the following code.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-docker"]},"children":[{"type":"text","value":"FROM docker.io/golang:alpine\n\nWORKDIR /go/src/app\nADD . .\n\nRUN go build -o helloworld main.go\nEXPOSE 6111\nCMD [\"./helloworld\"]\n"}]}]},{"type":"element","tagName":"h1","properties":{"id":"deploy-your-first-kubernetes-cluster"},"children":[{"type":"text","value":"Deploy your first Kubernetes cluster"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now you should have a Kubernetes cluster up and running.\nExamine the cluster and identity of the following details.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"From the kubeconfig, identify:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"the IP and port of the API server\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"authentication mechanism\n"}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"From the cluster using kubectl commands to identify:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"endpoints of the control plane and add-ons\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"amount of nodes\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"node internal IP\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"the pod CIDR allocate to the node\n"}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-3"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"From my setup with the given Vagrantfile from the lesson repo — i.e., after installing k3s in the virtual machine — you can inspect the kubeconfig located at "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/etc/rancher/k3s/k3s.yaml"}]},{"type":"text","value":".\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The IP and the port of the API server is visible from there.\nIn my case, it is 127.0.0.1 at port 6443.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As for the authentication mechanism, I'm not sure.\nBoth the cluster and the user have an attached certificate data.\nIt seems to be using matching certificate data from the user and the cluster.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As for getting cluster-related information.\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Getting the endpoints of the control plane and the add-ons through "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl cluster-info"}]},{"type":"text","value":".\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"One way of getting the amount of nodes is through "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl get nodes"}]},{"type":"text","value":" where it will print the nodes and their information one line at a time.\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For the node's internal IP and the pod CIDR, both of them can be extracted with "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl describe node ${NODE_NAME}"}]},{"type":"text","value":".\n"}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-2"},"children":[{"type":"text","value":"Findings after solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"I mostly got it right.\nIt turns out there are "},{"type":"element","tagName":"a","properties":{"href":"https://kubernetes.io/docs/reference/access-authn-authz/authentication/"},"children":[{"type":"text","value":"different methods for authentication"}]},{"type":"text","value":".\nWhile the solution gave it as user and passwords, the kubeconfig I have seem to be using keys and certificates. "}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Also, you can get the configuration of the cluster with "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl config view"}]},{"type":"text","value":".\nPretty handy.\n"}]},{"type":"element","tagName":"h1","properties":{"id":"kubernetes-resources"},"children":[{"type":"text","value":"Kubernetes resources"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now you have learned many Kubernetes recourses, in this exercise, you will deploy the following resources using the kubectl command.\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a namespace\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"demo"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"label: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"tier: test"}]}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a deployment:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"nginx:alpine"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"nginx-apline"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"demo"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replicas: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"3"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"labels: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"app: nginx, tag: alpine"}]}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a service:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"expose the above deployment on port 8111\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"demo"}]}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a configmap:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"nginx-version"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"containing key-value pair: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"version=alpine"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"demo"}]}]}]}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-4"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This is practical test but it can summarized with a shell script.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-bash"]},"children":[{"type":"text","value":"# Create the namespace with the specified label.\nkubectl create namespaces demo\nkubectl label namespaces demo tier=test\n\n# Create the specified deployment.\nkubectl create deployment nginx-alpine --image=nginx:alpine --replicas=3 --namespace=demo\nkubectl label deployment nginx-alpine app=nginx tag=alpine --namespace=demo\n\n# Expose the deployment as a service.\nkubectl expose deployment nginx-alpine --namespace=demo --port=8111\n\n# Create the config map.\nkubectl create configmaps nginx-version --namespace=demo --from-literal=version=alpine\n"}]}]},{"type":"element","tagName":"h1","properties":{"id":"declarative-kubernetes-manifests"},"children":[{"type":"text","value":"Declarative Kubernetes manifests"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Kubernetes is widely known for its imperative and declarative management techniques.\nIn the previous exercise, you have deployed the following resources using the imperative approach.\nNow deploy them using the declarative approach.\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a namespace\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: demo\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"label: tier: test\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a deployment:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image: nginx:alpine\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name:nginx-apline\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: demo\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replicas: 3\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"labels: app: nginx, tag: alpine\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a service:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"expose the above deployment on port 8111\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: demo\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"a configmap:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: nginx-version\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"containing key-value pair: version=alpine\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace: demo\n"}]}]}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-5"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Since they ask for 4 resources, we need 4 manifests.\nWe'll create four YAML manifests for this exercise.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The following manifests are created with the option to print the resources in YAML format with some modifications.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here's one for the namespace.\nThe command used to make the template is "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl create namespace demo --dry-run=client --output=yaml"}]},{"type":"text","value":".\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\nkind: Namespace\nmetadata:\n creationTimestamp: null\n name: demo\n labels:\n tier: test\nspec: {}\nstatus: {}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The manifest for the deployment.\nThe command used to create the starting template is "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl create deployment nginx-alpine --namespace=demo --replicas=3 --image=nginx:alpine --dry-run=client --output=yaml"}]},{"type":"text","value":".\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n creationTimestamp: null\n labels:\n app: nginx\n tag: alpine\n name: nginx-alpine\n namespace: demo\nspec:\n replicas: 3\n selector:\n matchLabels:\n app: nginx-alpine\n strategy: {}\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: nginx-alpine\n spec:\n containers:\n - image: nginx:alpine\n name: nginx\n resources: {}\nstatus: {}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The service manifest should be created after the deployment manifest is applied (i.e., "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl apply -f deployment.yaml"}]},{"type":"text","value":").\nIt is created with "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl expose deploy nginx-alpine --port=8111 --dry-run=client --output=yaml --namespace=demo"}]},{"type":"text","value":".\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\nkind: Service\nmetadata:\n creationTimestamp: null\n labels:\n app: nginx\n tag: alpine\n name: nginx-alpine\n namespace: demo\nspec:\n ports:\n - port: 8111\n protocol: TCP\n targetPort: 8111\n selector:\n app: nginx-alpine\nstatus:\n loadBalancer: {}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The resulting YAML output is from running the command (i.e., "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl create configmap nginx-version --from-literal=version=alpine --dry-run=client --output=yaml --namespace=demo"}]},{"type":"text","value":").\nNo cleaning up is required.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\ndata:\n version: alpine\nkind: ConfigMap\nmetadata:\n creationTimestamp: null\n name: nginx-version\n namespace: demo\n"}]}]},{"type":"element","tagName":"h2","properties":{"id":"findings-after-solution-3"},"children":[{"type":"text","value":"Findings after solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Aside from the mostly correct answers, I also found out "},{"type":"element","tagName":"code","properties":{"className":["inline-code"]},"children":[{"type":"text","value":"kubectl get all -n demo"}]},{"type":"text","value":" to get all of the resources in the specified namespace.\nPretty handy for inspecting application-specific resources.\n"}]},{"type":"element","tagName":"h1","properties":{"id":"continuous-application-deployment"},"children":[{"type":"text","value":"Continuous application deployment"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Create a new GitHub Actions in the "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"/.github/workflows/docker-build.yml"}]},{"type":"text","value":" that will build and push the Docker image for a Python web application, with the following requirements:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Image name: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"python-helloworld"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Tag: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"latest"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Platforms: "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"platforms: linux/amd64,linux/arm64"}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"GitHub marketplace has a rich suite of upstream actions that can be easily integrated within a repository.\nOne of the upstream action is "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/marketplace/actions/build-and-push-docker-images"},"children":[{"type":"text","value":"Build and Push Docker images"}]},{"type":"text","value":", which can be used to implement the required CI task.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The above GitHub action uses DockerHub Tokens and encrypted GitHub secrets to login into DockerHub and to push new images.\nTo set up these credentials refer to the following resources:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Create "},{"type":"element","tagName":"a","properties":{"href":"https://www.docker.com/blog/docker-hub-new-personal-access-tokens/"},"children":[{"type":"text","value":"DockerHub Tokens"}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Create "},{"type":"element","tagName":"a","properties":{"href":"https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets"},"children":[{"type":"text","value":"GitHub encrypted secrets"}]}]}]}]},{"type":"element","tagName":"h2","properties":{"id":"solution-6"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"After creating a DockerHub access token (that serves as an alternative to passwords) and creating a GitHub encrypted secret, the workflow should now work.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here's the resulting GitHub Actions workflow file:\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"name: Docker build image\non: [push]\njobs:\n docker:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v2\n - name: Set up QEMU\n uses: docker/setup-qemu-action@v1\n - name: Setup Docker Buildx\n uses: docker/setup-buildx-action@v1\n - name: Login to DockerHub\n uses: docker/login-action@v1\n with:\n username: ${{ secrets.DOCKERHUB_USERNAME }}\n password: ${{ secrets.DOCKERHUB_TOKEN }}\n - name: Build and push\n id: docker_build\n uses: docker/build-push-action@v2\n with:\n push: true\n tags: ${{ secrets.DOCKERHUB_USERNAME }}/python-helloworld:latest\n platforms: linux/amd64,linux/arm64\n - name: Image digest\n run: echo ${{ steps.docker_build.outputs.digest }}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This is shamelessly ripped off from the sample from the "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/marketplace/actions/build-and-push-docker-images"},"children":[{"type":"text","value":"GitHub Actions page"}]},{"type":"text","value":".\nI realized it's basically the answer for this exercise.\nEmbarrassing that I spent an hour for this.\n"}]},{"type":"element","tagName":"h1","properties":{"id":"the-cd-fundamentals"},"children":[{"type":"text","value":"The CD fundamentals"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Continuous Delivery (CD) is the ability to get code changes reliably to production environments.\nThis practice should be automated and should enable developers to provide value to consumers efficiently.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"In this exercise, you will use ArgoCD to automate the delivery of an application to a Kubernetes cluster.\n"}]},{"type":"element","tagName":"h2","properties":{"id":"solution-7"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The manifest is really the same as the example manifest from the walkthrough except with a different path pointing to the required manifests.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n name: ngnix-alpine\n namespace: argocd\nspec:\n destination:\n namespace: default\n server: https://kubernetes.default.svc\n project: default\n source:\n path: exercises/manifests\n repoURL: https://github.com/udacity/nd064_course_1\n targetRevision: HEAD\n syncPolicy: {}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Then integrate it into the cluster with the following command.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"kubectl apply -f argocd-nginx-alpine.yaml\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"You should then see the project on the ArgoCD application list where it requires an initial sync.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For assurance, you can check to see if the resources are deployed.\nContinuing from "},{"type":"element","tagName":"a","properties":{"href":"/Declarative%20Kubernetes%20manifests"},"children":[{"type":"text","value":"Declarative Kubernetes manifests"}]},{"type":"text","value":", the resources are mostly in the "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"demo"}]},{"type":"text","value":" namespace.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"kubectl get pod -n demo\nkubectl get deploy -n demo\nkubectl get rs -n demo\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"You should see them up and running.\n"}]},{"type":"element","tagName":"h1","properties":{"id":"configuration-managers"},"children":[{"type":"text","value":"Configuration managers"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Using the manifests provided in the course repository, create a helm chart (Chart.yaml, templates, values.yaml) that will template the following parameters:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace name\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replica count\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"tag\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"pull policy\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"resources\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"requests for CPU and memory\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"service\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"port\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"type (e.g. ClusterIP)\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"configmap data (e.g. the key-value pair)\n"}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The chart details should be as following:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"name: nginx-deployment\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"version: 1.0.0\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"keywords: nginx\n"}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Once the Helm chart is available make sure that a default values.yaml file is available. This values file will be used as a default input file for the Helm chart. The values.yaml file should have the following specification:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"values.yaml\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace name: demo\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replica count: 3\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image repository: nginx\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image tag: alpine\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image pull policy: IfNotPresent\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"resources: CPU 50m and memory 256Mi\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"service type: ClusterIP\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"service port: 8111\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"configmap data: \"version: alpine\"\n"}]}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Next, create 2 values files with the following specifications:\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"values-staging.yaml\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace name: staging\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replica count: 1\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image repository: nginx\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image tag: 1.18.0\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"resources: CPU 50m and memory 128Mi\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"configmap data: \"version: 1.18.0\"\n"}]}]}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"values-prod.yaml\n"}]},{"type":"element","tagName":"ul","properties":{},"children":[{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"namespace name: prod\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"replica count: 2\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image repository: nginx\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"image tag: 1.17.0\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"resources: CPU 70m and memory 256Mi\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"service port: 80\n"}]}]},{"type":"element","tagName":"li","properties":{},"children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"configmap data: \"version: 1.17.0\"\n"}]}]}]}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Finally, using the values files above (values-prod, values-staging), create 2 ArgoCD application, nginx-staging and nginx-prod respectively. These should deploy the nginx Helm Chart referencing each input values files.\n"}]},{"type":"element","tagName":"h2","properties":{"id":"solution-8"},"children":[{"type":"text","value":"Solution"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"With the given manifests, we'll have to create a Helm package (or a Chart).\nHere's what the file structure of the chart.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{},"children":[{"type":"text","value":"helm-nginx\n├── templates\n│   ├── configmap.yaml\n│   ├── deployment.yaml\n│   ├── namespace.yaml\n│   └── service.yaml\n├── Chart.yaml\n├── values.yaml\n├── values-prod.yaml\n└── values-staging.yaml\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"The chart definition is the following file.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\nname: nginx-deployment\nversion: 1.0.0\nkeywords:\n- nginx\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"With the chart definition, we move on to the values to be used in the templates.\nIt's up to you how to structure the data but here's my solution on it.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"# namespace refers to the Kubernetes namespace resource.\nnamespace:\n name: demo\n\n# replicaCount is the number of instances to run in a replica set.\nreplicaCount: 3\n\n# image contains the detail of the container image to be used\nimage:\n repository: nginx\n tag: alpine\n pullPolicy: IfNotPresent\n\n# resources dictate the amount to spend\nresources:\n cpu: 50m\n memory: 256Mi\n\n# service configures the Kubernetes service resource\nservice:\n type: ClusterIP\n port: 8111\n\n# configmap configures the Kubernetes configmap resource\nconfigmap:\n data: \"version: alpine\"\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As for different versions of the values such as...\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"...for development version ("},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"values-staging.yaml"}]},{"type":"text","value":")...\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"namespace:\n name: staging\nreplicaCount: 1\nimage:\n repository: nginx\n tag: 1.18.0\nresources:\n cpu: 50m\n memory: 128Mi\nconfigmap:\n data: \"version: 1.18.0\"\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"...and for production ("},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"values-prod.yaml"}]},{"type":"text","value":").\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"namespace:\n name: prod\nreplicaCount: 2\nimage:\n repository: nginx\n tag: 1.17.0\nresources:\n cpu: 70m\n memory: 256Mi\nconfigmap:\n data: \"version: 1.17.0\"\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"With the templates, it is already given to us from the course exercise repo.\nWe just have to parameterize some of the values from the value file.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here's one for the configmap...\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\ndata:\n {{ .Values.configmap.data }}\nkind: ConfigMap\nmetadata:\n name: nginx-version\n namespace: {{ .Values.namespace.name }}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"..., deployment...\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: nginx\n tag: alpine\n name: nginx-alpine\n namespace: {{ .Values.namespace.name }}\nspec:\n replicas: {{ .Values.replicaCount }}\n selector:\n matchLabels:\n app: nginx\n tag: alpine\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n labels:\n app: nginx\n tag: alpine\n spec:\n containers:\n - image: {{ .Values.image.repository }}:{{ .Values.image.tag }}\n imagePullPolicy: {{ .Values.image.pullPolicy }}\n name: nginx-alpine\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"..., namespace...\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\nkind: Namespace\nmetadata:\n labels:\n tier: test\n name: {{ .Values.namespace.name }}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"..., and service.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: v1\nkind: Service\nmetadata:\n labels:\n app: nginx\n tag: alpine\n name: nginx-alpine\n namespace: {{ .Values.namespace.name }}\nspec:\n ports:\n - port: {{ .Values.service.port }}\n protocol: TCP\n targetPort: {{ .Values.service.port }}\n selector:\n app: nginx\n tag: alpine\n type: {{ .Values.service.type }}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"To deploy it in ArgoCD, we'll just have to specify it in the ArgoCD manifest.\nSince ArgoCD is heavily a GitOps tool, we have to put the files in a Git repo.\nFor now, let's assume the course repo as ours. "},{"type":"text","value":"\nJust change the Git repo URL and the path if you have your own version.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Additionally, the exercise requires to deploy two ArgoCD applications: one for staging (i.e., "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"nginx-staging"}]},{"type":"text","value":") and production version (i.e., "},{"type":"element","tagName":"code","properties":{"className":["inline-verbatim"]},"children":[{"type":"text","value":"nginx-prod"}]},{"type":"text","value":") of the app.\n"}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here's one for the production version...\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n name: nginx-prod\n namespace: argocd\nspec:\n destination:\n namespace: default\n server: https://kubernetes.default.svc\n project: default\n source:\n helm:\n valueFiles:\n - values-prod.yaml\n path: structured/assets/challenges.suse-cloud-native-fundamentals-scholarship-program/helm-nginx\n repoURL: https://github.com/foo-dogsquared/wiki\n targetRevision: HEAD\n syncPolicy: {}\n"}]}]},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"...and the development version.\n"}]},{"type":"element","tagName":"pre","properties":{"className":["src-block"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-yaml"]},"children":[{"type":"text","value":"apiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n name: nginx-staging\n namespace: argocd\nspec:\n destination:\n namespace: default\n server: https://kubernetes.default.svc\n project: default\n source:\n helm:\n valueFiles:\n - values-staging.yaml\n path: structured/assets/challenges.suse-cloud-native-fundamentals-scholarship-program/helm-nginx\n repoURL: https://github.com/foo-dogsquared/wiki\n targetRevision: HEAD\n syncPolicy: {}\n"}]}]}]},"backlinks":[{"path":"/literature.suse-cloud-native-fundamentals-scholarship-program","title":"SUSE Cloud native fundamentals scholarship program"}]},"__N_SSG":true},"page":"/[[...slug]]","query":{"slug":["challenges.suse-cloud-native-fundamentals-scholarship-program"]},"buildId":"Ie9t5zutrXP6Of75Cb5xF","assetPrefix":"/wiki","nextExport":false,"isFallback":false,"gsp":true}</script><script nomodule="" src="/wiki/_next/static/chunks/polyfills-99d808df29361cf7ffb1.js"></script><script src="/wiki/_next/static/chunks/main-ae4733327bd95c4ac325.js" async=""></script><script src="/wiki/_next/static/chunks/webpack-50bee04d1dc61f8adf5b.js" async=""></script><script src="/wiki/_next/static/chunks/framework.9d524150d48315f49e80.js" async=""></script><script src="/wiki/_next/static/chunks/commons.0e1c3f9aa780c2dfe9f0.js" async=""></script><script src="/wiki/_next/static/chunks/pages/_app-8e3d0c58a60ec788aa69.js" async=""></script><script src="/wiki/_next/static/chunks/940643274e605e7596ecea1f2ff8d83317a3fb76.4841a16762f602a59f00.js" async=""></script><script src="/wiki/_next/static/chunks/pages/%5B%5B...slug%5D%5D-1aa198f87ede1cd0e1dc.js" async=""></script><script src="/wiki/_next/static/Ie9t5zutrXP6Of75Cb5xF/_buildManifest.js" async=""></script><script src="/wiki/_next/static/Ie9t5zutrXP6Of75Cb5xF/_ssgManifest.js" async=""></script></body></html>