Last active
November 7, 2018 21:02
-
-
Save ryanj/a42cf467af9557c5d8a49bc45a7bd515 to your computer and use it in GitHub Desktop.
"Hands-on Intro to Kubernetes & OpenShift" at #NoFluff 2018 http://bit.ly/nfjs-k8s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<section data-background-transition="zoom" data-transition='zoom'> | |
<section data-background-transition="none-in zoom-out" data-transition="zoom" id="HANDS-ON" data-background="https://gist.githubusercontent.com/ryanj/9181e48c0dd8e6b45d692a11d5a72bd5/raw/9892bcb34a809c25c24c43a2ea33bb69b7684f0b/node-js-interactive-1.jpg" data-background-color="black" data-background-size="cover" data-background-position="top"> | |
<div style="-ms-transform: rotate(-6deg);-webkit-transform: rotate(-6deg);transform: rotate(-6deg); color: white;font-size: 43px;background: black;position: absolute;top: 11%;left: 29%;width: min-content;width:-moz-fit-content;" class='fragment fade-right'>NO FLUFF</div> | |
<h3 style="color:white;padding-top:23%;">Hands-on Intro to</h3> | |
<h1 style="color:white;font-size:113px;">Kubernetes & OpenShift</h1> | |
<p style="padding-top:1.5%" class='fragment grow'><a style="font-weight:bold;" href="http://gist-reveal.it/a42cf467af9557c5d8a49bc45a7bd515?theme=4f6d86229cc5ee8c07f5a70c1e3fcc41">bit.ly/nfjs-k8s</a></p> | |
<p><a href="https://nofluffjuststuff.com/conference/chicago/2018/10/workshop_signup">Friday Nov. 2nd 8:30am-1pm<br/>Great Lakes Software Symposium Pre-Conference Training</a></p></section> | |
<section style="color:white;" data-background-transition='concave-in slide-out' data-transition='concave-in slide-out' data-background-transition='fade' data-background='#000000' data-background-color="black" id='presented-by'> | |
<p style="color:white;">presented by…</p> | |
<div class='fragment fade-right' style='width:45%; float:left;'> | |
<p><a href="http://twitter.com/ryanj/"><img alt="ryanj" src="http://ryanjarvinen.com/images/ryanj-mestrefungo-com.gif" style="width:70%" /></p> | |
<p><a href="http://twitter.com/ryanj/">Ryan Jarvinen @ryanj</a></p> | |
</div> | |
<div class='fragment fade-up' style='width:10%;float:left;margin-top: 13%;font-size: 250%;font-weight: bold;color:white;'>&</div> | |
<div class='fragment fade-left' style='width:45%; float:left;'> | |
<p><a href="http://twitter.com/jankleinert"><img alt="Jan Kleinert" src="https://i.imgur.com/HoF47Kj.jpg" style="width:70%"/></p> | |
<p><a href="http://twitter.com/jankleinert">Jan Kleinert @jankleinert</a></p> | |
</div> | |
<br/> | |
<!-- <p style="color:white;">presented by <a href="http://twitter.com/ryanj/">@ryanj</a>, Developer Advocate at Red Hat</p> | |
<p style="color:white;" class='fragment fade-up'><a href="http://twitter.com/ryanj/"><img alt="ryanj" src="http://ryanjarvinen.com/images/ryanj-mestrefungo-com.gif" style="width:50%"/><br/>@ryanj</p> --> | |
</section> | |
<section id='brought-to-you-by' data-background='black' data-background-color="black"> | |
<p style="color:white;">brought to you by</p> | |
<p style="color:white;"><a href="https://redhat.com"><img alt="Red Hat logo" src="https://i.imgur.com/ArZFG3e.png" /></a></p> | |
</section> | |
<!-- | |
<section id='bought-to-you-by' background-transition="concave-in convex-out" data-background-transition="convex-in concave-out" data-background-color="black" data-background="black" > | |
<p style="color:white;">bought to you by?</p> | |
<p style="color:white;" class='fragment fade-up'><a href="https://redhat.com"><img alt="IBM / Red Hat logo" src="https://i.imgur.com/jTaFIID.jpg" /></a></p> | |
</section> | |
--> | |
</section> | |
<section data-transition='convex'> | |
<section id='introduction'> | |
<h1>Introduction</h1> | |
</section> | |
<section id='survey'> | |
<h3>Intro Survey / Who are you?</h3> | |
<ol> | |
<li class='fragment'>do you have any experience using containers?</li> | |
<li class='fragment'>have you completed all of the <a href="#/laptop-setup">laptop setup</a> tasks?</li> | |
<li class='fragment'>do you have any experience using Kubernetes?</li> | |
<li class='fragment'>do you consider yourself to be proficient with the <code>oc</code> or <code>kubectl</code> cli tools?</li> | |
<li class='fragment'>can you name five basic Kubernetes primitives or resource types?</li> | |
<li class='fragment'>do you have a plan for iterative development using containers?</li> | |
</ol> | |
</section> | |
</section> | |
<section> | |
<section data-transition='convex' id='agenda'> | |
<h2>Workshop Agenda</h2> | |
<ul style='list-style-type: none;'> | |
<li class='fragment'><a href="#/introduction">Introduction</a> | |
<ul style='list-style: circle;'> | |
<li class='fragment'><a href="#/agenda">Agenda</a></li> | |
<li class='fragment'><a href="#/kubernetes">Overview</a></li> | |
<li class='fragment'><a href="#/laptop-setup">Laptop Setup</a></li> | |
</ul> | |
</li> | |
<li class='fragment'><a href="#/kubernetes-basics">Kubernetes Basics</a> | |
<ul style='list-style: circle;'> | |
<li class='fragment'><a href="#/basic-resource-types">Learn five basic resource types</a></li> | |
</ul> | |
</li> | |
<li class='fragment'><a href="#/hands-on-with-openshift">Hands-On with OpenShift</a> | |
<ul style='list-style-type: circle;'> | |
<li class='fragment'><a href="#/build">Build</a>, <a href="#/automate">Automate</a>, <a href="#/iterate">Iterate</a>, <a href="#/collaborate">Collaborate</a></li> | |
</ul> | |
</li> | |
<li class='fragment'><a href="#/wrap-up">Wrap Up / Q&A</a></li> | |
</ul> | |
</section> | |
<section id='kubernetes'> | |
<h1 id="kubernetes">Kubernetes</h1> | |
<ul> | |
<li><a href="https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/">is</a>: an ops tool; a collection of APIs for managing container-based workloads; </li> | |
<li><a href="https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/#what-kubernetes-is-not">is not</a>: a PaaS</li> | |
</ul> | |
<p class='fragment fade-up'>Conceptually: a distributed OS "kernel" that allows for workloads, platform nodes, and control-plane components that are each modular, highly-availabile, and independently scalable</p> | |
</section> | |
<section id='kubernetes-problem-space' data-markdown> | |
#### problem space: distributed computing | |
[!["scenes from distributed systems, by @b0rk"](https://pbs.twimg.com/media/Dqmn6ACWoAA3JIp.jpg)](https://twitter.com/b0rk/status/1056560207562711041) | |
</section> | |
<section data-markdown> | |
Kubernetes uses | |
## etcd | |
to keep track of the cluster's state | |
![etcd logo](https://raw.githubusercontent.com/coreos/etcd/master/logos/etcd-glyph-color.png) | |
* distributed key-value store | |
* implements the [RAFT](https://raft.github.io/raft.pdf) consensus protocol | |
* CAP theorum: [CAP twelve years later](https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed) | |
</section> | |
<section data-markdown> | |
## Etcd cluster sizes | |
Fault-tolerance sizing chart: | |
![etcd cluster sizing chart](http://cloudgeekz.com/wp-content/uploads/2016/10/etcd-fault-tolerance-table.png) | |
</section> | |
<section id='all-the-most-popular-platforms' data-background='black'> | |
<h2 style="color: white;">2017 Octoverse Report</h2> | |
<blockquote style="color:white;">🏆 Most-discussed on GitHub in 2017!</blockquote> | |
<p style="color: white;"><a href="http://octoverse.github.com">http://octoverse.github.com</a></p> | |
<p style="color:white;"><a href="https://kubernetes.io/blog/2018/04/25/open-source-charts-2017">Kubernetes Community - Top of the Open Source Charts in 2017</a></p> | |
<p style="color:white;" class='fragment fade-up'><img alt="octoverse discussion leaderboard" src="https://d33wubrfki0l68.cloudfront.net/d5511b44cad712d8ba3e7139448c081366808fa0/27939/images/blog-logging/2018-04-24-open-source-charts-2017/most-discussed.png" /></p> | |
</section> | |
<section id='openshift'> | |
<h1 id="openshift">OpenShift</h1> | |
<ul> | |
<li>includes, extends, & is a distribution of: Kubernetes</li> | |
<li>adds: developer workflows, service catalog (and broker APIs), Multi-tenant security, a container registry, distributed metrics, logs, and more …</li> | |
</ul> | |
<p class='fragment fade-up'>Conceptually: a multi-tenant PaaS that operates consistently over any cloud infrastructure (even BYO bare-metal hardware)!</p> | |
</section> | |
<section id='more-info'> | |
<h3>More Information</h3> | |
<ul style='list-style-type:none;'> | |
<li>Kubernetes: | |
<ul style='list-style-type:circle;'> | |
<li>Sources and Official Releases:<br/> | |
<a href="http://github.com/kubernetes/kubernetes">http://github.com/kubernetes/kubernetes</a></li> | |
<li>Docs: <a href="http://kubernetes.io/docs/home/">http://kubernetes.io/docs/home/</a></li> | |
</ul> | |
</li> | |
<li>OpenShift | |
<ul style='list-style-type:circle;'><li>Sources and Official Releases:<br/> | |
<a href="http://github.com/openshift/origin/">http://github.com/openshift/origin/</a></li> | |
<li>Docs: <a href="https://docs.openshift.com/">https://docs.openshift.com/</a></li></ul></li> | |
</ul> | |
</section> | |
</section> | |
<section> | |
<!-- | |
<section id='laptop-setup' data-markdown> | |
## Workshop Requirements | |
Minimal requirements this morning: | |
* [*a web browser with JS enabled →*](#/laptop-setup-2) | |
To run this workshop locally, you'd need: | |
1. [kubectl](#/kubectl) | |
2. [oc](#/oc) | |
3. [bash](#/bash) | |
3. [minishift](#/minishift) | |
↓ | |
</section> | |
--> | |
<section id='laptop-setup' data-markdown> | |
## Workshop Requirements | |
To run this workshop locally, you'll need: | |
1. [kubectl](#/kubectl) | |
2. [oc](#/oc) | |
3. [bash](#/bash) | |
3. [minishift](#/minishift) | |
↓ | |
</section> | |
<section id='kubectl'> | |
<h3>Install <code>kubectl</code></h3> | |
<p>For detailed installation notes, see the <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/"><code>kubectl</code> install doc</a></p> | |
<p>One line install for linux/amd64:</p> | |
<pre><code contenteditable>curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ | |
</code></pre> | |
<p>One line install for macOS:</p> | |
<pre><code contenteditable>curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ | |
</code></pre> | |
<br/> | |
<p>To verify <code>kubectl</code> availability, try running:</p> | |
<pre><code contenteditable>kubectl help</code></pre> | |
</section> | |
<section id='oc'> | |
<h3>Install oc</h3> | |
<p>For detailed installation notes, see the <a href="https://docs.openshift.org/latest/cli_reference/get_started_cli.html"><code>oc</code> installation doc</a></p> | |
<p>One line install for linux/amd64:</p> | |
<pre><code contenteditable>curl -Lo oc.tar.gz https://github.com/openshift/origin/releases/download/v3.11.0/openshift-origin-client-tools-v3.11.0-0cbc58b-linux-64bit.tar.gz && tar xvzf oc.tar.gz */oc && sudo mv $_ /usr/local/bin/ && rm -d openshift-origin-client-tools-* && rm oc.tar.gz</code></pre> | |
<p>One line install for macOS:</p> | |
<pre><code contenteditable>curl -Lo oc.zip https://github.com/openshift/origin/releases/download/v3.11.0/openshift-origin-client-tools-v3.11.0-0cbc58b-mac.zip && tar xvzf oc.zip oc && sudo mv oc /usr/local/bin/ && rm oc.zip</code></pre> | |
<br/> | |
<p>To verify <code>oc</code> availability, try running:</p> | |
<pre><code contenteditable>oc help</code></pre> | |
</section> | |
<section id='bash'> | |
<h3>Bash for Windows</h3> | |
<p>Windows users should install the <code>Windows Subsystem for Linux</code> and related command-line tools:</p> | |
<p>Enable <b>Control Panel > Programs > Windows Features > Windows Subsystem for Linux</b></p> | |
</section> | |
<section id='minishift'> | |
<h3>Install <code>minishift</code></h3> | |
<p>For detailed installation notes, see the <a href="https://github.com/minishift/minishift/releases"><code>minishift</code> release notes</a></i></p> | |
<p>One line install for linux/amd64:</p> | |
<pre><code contenteditable>curl -Lo minishift.tgz https://github.com/minishift/minishift/releases/download/v1.25.0/minishift-1.25.0-linux-amd64.tgz && tar xvzf minishift.tgz */minishift && sudo mv $_ /usr/local/bin/ && rm -d minishift-* && rm minishift.tgz</code></pre> | |
<p>One line install for macOS:</p> | |
<pre><code contenteditable>curl -Lo minishift.tgz https://github.com/minishift/minishift/releases/download/v1.25.0/minishift-1.25.0-darwin-amd64.tgz && tar xvzf minishift.tgz */minishift && sudo mv $_ /usr/local/bin/ && rm -d minishift-* && rm minishift.tgz</code></pre> | |
<p>Optionally, customize your cluster's memory or cpu allocation:</p> | |
<pre><code contenteditable>minishift config set memory 4096 | |
minishift config set cpus 2 | |
minishift config set openshift-version latest</code></pre> | |
<p>to verify <code>minishift</code> availability:</p> | |
<pre><code contenteditable>minishift version</code></pre> | |
</section> | |
<section id='minishift-virt'> | |
<h4>Virtualization Plugins</h4> | |
<p>See the <a href="https://docs.openshift.org/latest/minishift/getting-started/installing.html#install-prerequisites"><code>minishift</code> installation guide</a> for <a href="https://docs.openshift.org/latest/minishift/getting-started/setting-up-driver-plugin.html">virt driver plugin requirements</a></p> | |
<br/> | |
<p>If your minishift environment does not boot correctly:</p> | |
<ol> | |
<li>Minishift <a href="https://docs.openshift.org/latest/minishift/getting-started/installing.html#install-prerequisites">requires an OS virtualization solution</a>. Most OSes already include one!</li> | |
<li><a href="https://docs.openshift.org/latest/minishift/getting-started/setting-up-driver-plugin.html">Install the appropriate driver plugin</a> for your system</li> | |
<li>Use the <a href="https://docs.openshift.org/latest/minishift/getting-started/setting-up-driver-plugin.html"><code>--vm-driver</code></a> flag to select specific plugins by name</li> | |
</ol> | |
<pre><code contenteditable>minishift start --vm-driver=virtualbox</code></pre> | |
</section> | |
<section id='minishift-basics' markdown> | |
<h2>Minishift Basics</h2> | |
<p><code>minishift</code> provides an easy way to run OpenShift locally:</p> | |
<pre><code contenteditable>minishift start</code></pre> | |
<p>When you are done, halt the VM to free up system resources:</p> | |
<pre><code contenteditable>minishift stop</code></pre> | |
<p>Need a fresh start? Delete your VM instance with:</p> | |
<pre><code contenteditable>minishift delete</code></pre> | |
</section> | |
</section> | |
<!-- | |
<section id='laptop-setup-2' data-markdown> | |
## Lab Requirements | |
Claim a username by adding ***your name*** to the right of one of the available **USER_ID**s in this doc: | |
[workshop-users.apps.nofluff-13cb.openshiftworkshop.com/p/workshop](http://workshop-users.apps.nofluff-13cb.openshiftworkshop.com/p/workshop) | |
Next, sign in with your new **USER_ID** and a password of "**openshift**" at the following url: | |
[terminal-workshop.apps.nofluff-13cb.openshiftworkshop.com](https://terminal-workshop.apps.nofluff-13cb.openshiftworkshop.com) | |
</section> | |
--> | |
<section> | |
<section> | |
<section data-transition='zoom-in convex-out' id='ready'> | |
<h1><i>Ready?</i></h1> | |
<br/> | |
<div class='fragment fade-up'> | |
<p>Before we get started, you'll need to configure your shell with the following environment variables:</p> | |
<pre><code contenteditable>export API_SERVER=https://$(minishift ip):8443 | |
export WORKLOAD_SERVER=$(minishift ip) | |
export USER_ID=developer | |
export NAMESPACE=myproject</code></pre> | |
</div> | |
<div class='fragment fade-up'> | |
<p>Verify that your cli tools are configured to connect to your Kubernetes environment:<br/> | |
<pre><code contenteditable>kubectl version</code></pre> | |
<p>The output should include your <code>kubectl</code> version info, and the release version of the kubernetes API server (when available)</p> | |
</div> | |
</section> | |
<section data-background-transition="zoom"> | |
<h1><i>Let's Go!</i></h1> | |
</section> | |
</section> | |
<!-- | |
<section> | |
<section data-transition='zoom-in convex-out' id='ready'> | |
<h1><i>Ready?</i></h1> | |
<br/> | |
<div class='fragment fade-up'> | |
<p>Before we get started, you'll need to configure your <br/><code>~/.kube/config</code> file. We will do this by running:</p> | |
<pre><code contenteditable>#export API_SERVER=https://$(minishift ip):8443 | |
#export WORKLOAD_SERVER=$(minishift ip) | |
#export USER_ID=developer | |
#export NAMESPACE=myproject | |
export API_SERVER=https://master.nofluff-13cb.openshiftworkshop.com | |
export WORKLOAD_SERVER=www.apps.nofluff-13cb.openshiftworkshop.com | |
export USER_ID=userN | |
export NAMESPACE=userN | |
oc login $API_SERVER -u $USER_ID</code></pre> | |
</div> | |
<div class='fragment fade-up'> | |
<p>Confirm that you've been authenticated by running:</p> | |
<pre><code contenteditable>oc whoami</code></pre> | |
</div> | |
</section> | |
<section data-background-transition="zoom"> | |
<h1>All Set?</h1> | |
<div class='fragment fade-up'> | |
<p>Verify that your cli tools are configured to connect to your Kubernetes environment:<br/> | |
<pre><code contenteditable>kubectl version</code></pre> | |
<p>The output should include your <code>kubectl</code> version info, and the release version of the kubernetes API server (when available)</p> | |
</div> | |
</section> | |
<section data-background-transition="zoom"> | |
<h1><i>Let's Go!</i></h1> | |
</section> | |
</section> | |
--> | |
<section> | |
<section id='kubernetes-basics'> | |
<h1>Kubernetes Basics</h1> | |
<p>↓</p> | |
</section> | |
<section id='an-api' data-markdown> | |
Kubernetes provides… | |
# An API | |
API object primitives include the following attributes: | |
``` | |
kind | |
apiVersion | |
metadata | |
spec | |
status | |
``` | |
*mostly true | |
Extended Kubernetes API Reference: | |
http://k8s.io/docs/reference/generated/kubernetes-api/v1.12/ | |
</section> | |
<section data-transition="linear" id='basic-resource-types' data-markdown> | |
### Five Basic Resource Types: | |
1. [nodes](#/node) | |
2. [pods](#/po) | |
3. [services](#/svc) | |
4. [deployments](#/deployment) | |
5. [replicaSets](#/rs) | |
</section> | |
</section> | |
<section> | |
<section data-transition="linear" id='node' data-markdown> | |
### Nodes | |
A node is a host machine (physical or virtual) where containerized processes run. | |
Node activity is managed via one or more Master instances. | |
</section> | |
<section> | |
<p>Try using <code>kubectl</code> to list resources by type:</p> | |
<pre><code contenteditable>kubectl get nodes</code></pre> | |
</section> | |
<section> | |
<h3>Solution for Minishift users:</h3> | |
<p>Log in as an admin user (password "openshift")</p> | |
<pre><code contenteditable>minishift addon apply admin-user | |
oc login -u admin</code></pre> | |
<p>Try to list nodes using admin credentials:</p> | |
<pre><code contenteditable>kubectl get nodes</code></pre> | |
<p>Now try using <code>curl</code> to make the same request:</p> | |
<pre><code contenteditable>curl -k -H"Authorization: Bearer $(oc whoami -t)" https://$(minishift ip):8443/api/v1/nodes</code></pre> | |
<p>We won't need admin priveleges for the remaining content, so let's swap back to the "developer" user:</p> | |
<pre><code contenteditable>oc login -u developer</code></pre> | |
</section> | |
<section> | |
<h3>Observations:</h3> | |
<ul> | |
<li class='fragment'>Designed to exist on multiple machines (distributed system)<ul> | |
<li class='fragment'>built for high availability</li> | |
<li class='fragment'>platform scale out</li></ul></li> | |
<li class='fragment'>The Kubernetes API checks auth credentials and restricts access to Etcd, our platform's distributed consensus store</li> | |
<li class='fragment'>New openshift users are not granted admin access by default</li> | |
<li class='fragment'>Your JS runs on nodes!</li> | |
</ul> | |
</section> | |
</section> | |
<section> | |
<section data-transition="linear" id='po' data-markdown> | |
### Pods | |
A group of one or more co-located containers. Pods represent your minimum increment of scale. | |
> "Pods Scale together, and they Fail together" @theSteve0 | |
</section> | |
<section> | |
<p>Try using <code>kubectl</code> to list resources by type:</p> | |
<pre><code contenteditable>kubectl get pods</code></pre> | |
<p>Create a new resource from a json object specification:</p> | |
<pre><code contenteditable>curl -LO https://raw.githubusercontent.com/jankleinert/hello-workshop/master/pod.json | |
cat pod.json | |
curl -k -H"Authorization: Bearer $(oc whoami -t)" -H'Content-Type: application/json' $API_SERVER/api/v1/namespaces/$NAMESPACE/pods -X POST --data-binary @pod.json</code></pre> | |
<p>Attempt the same using <code>kubectl</code>:</p> | |
<pre><code contenteditable>kubectl create -f https://raw.githubusercontent.com/jankleinert/hello-workshop/master/pod.json</code></pre> | |
</section> | |
<section> | |
<!-- | |
<p>Request the same info using <code>curl</code>:</p> | |
<pre><code contenteditable>curl -k -H'Authorization: Bearer $(oc whoami -t)' $(minishift ip):8443/api/v1/namespaces/$(oc whoami)/pods/hello-k8s</code></pre> | |
--> | |
<p>List pods by type using <code>curl</code>:</p> | |
<pre><code contenteditable>curl -k -H"Authorization: Bearer $(oc whoami -t)" $API_SERVER/api/v1/namespaces/$NAMESPACE/pods</code></pre> | |
<p>Fetch an individual resource by <code>type/id</code>; output as <code>json</code>:</p> | |
<pre><code contenteditable>kubectl get pod hello-k8s -o json</code></pre> | |
<p>Attempt the same using <code>curl</code>:</p> | |
<pre><code contenteditable>curl -k -H"Authorization: Bearer $(oc whoami -t)" $API_SERVER/api/v1/namespaces/$NAMESPACE/pods/hello-k8s</code></pre> | |
<p class='fragment'>Notice any changes between the initial json podspec and the API response?</p> | |
</section> | |
<section> | |
<p>Request the same info, but output the results as structured yaml:</p> | |
<pre><code contenteditable>kubectl get pod hello-k8s -o yaml</code></pre> | |
<p>Print human-readable API output:</p> | |
<pre><code contenteditable>kubectl describe pod/hello-k8s</code></pre> | |
</section> | |
<section> | |
<h3 id="observations-">Observations:</h3> | |
<ul> | |
<li class='fragment'>API resources provide declarative specifications with asyncronous fulfilment of requests<ul> | |
<li class='fragment'>you set the <code>spec</code>, the platform will populate the <code>status</code></li></ul></li> | |
<li class='fragment'>automated health checking for PID1 in each container</li> | |
<li class='fragment'>Pods are scheduled to be run on nodes </li> | |
<li class='fragment'>The API ambidextriously supports both json and yaml</li> | |
</ul> | |
</section> | |
<!-- | |
<section data-markdown> | |
</section> | |
--> | |
</section> | |
<section> | |
<section data-transition="linear" id='svc' data-markdown> | |
### Services | |
Services (svc) establish a single endpoint for a collection of replicated pods, distributing traffic based on label selectors | |
In our K8s modeling language they represent a load balancer. Their implementation may vary per cloud provider | |
</section> | |
<section id='connections'> | |
<h3>Contacting your App</h3> | |
<p>Expose the pod by creating a new <code>service</code> (or "loadbalancer"):</p> | |
<pre><code contenteditable>kubectl expose pod/hello-k8s --port 8080 --type=NodePort</code></pre> | |
<br/> | |
<p>Take a look at the resulting <code>{.spec.selector}</code> attribute:</p> | |
<pre><code contenteditable>kubectl get svc/hello-k8s -o json</code></pre> | |
</section> | |
<section id='jsonpath'> | |
<p>Try using a <a href="https://kubernetes.io/docs/reference/kubectl/jsonpath/">JSONpath</a> selector to find the assigned port number:</p> | |
<pre><code contenteditable>kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort}</code></pre> | |
<br/> | |
<p>Contact your newly-exposed pod via the associated nodePort:</p> | |
<pre><code contenteditable>echo $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<pre><code contenteditable>curl $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<!--<pre><code contenteditable>echo http://$(minishift ip):$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<pre><code contenteditable>curl http://$(minishift ip):$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
--> | |
</section> | |
<section> | |
<p>Schedule the deletion of all pods that are labeled with:</p> | |
<pre><code contenteditable>kubectl get pods -l run=hello-k8s</code></pre> | |
<pre><code contenteditable>kubectl delete pods -l run=hello-k8s</code></pre> | |
<br/> | |
<p>Contact the related service. What happens?:</p> | |
<!--<pre><code contenteditable>curl $(minishift ip):$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre>--> | |
<pre><code contenteditable>curl $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<p>Delete the service:</p> | |
<pre><code contenteditable>kubectl delete service hello-k8s</code></pre> | |
</section> | |
<section> | |
<h3 id="observations-">Observations:</h3> | |
<ul> | |
<li class='fragment'><em>"service"</em> basically means <em>"loadbalancer"</em></li> | |
<li class='fragment'>Label selectors can be used to organize workloads and manage groups of related resouces</li> | |
<li class='fragment'>The Service resource uses label selectors to discover where traffic should be directed</li> | |
<li class='fragment'>Pods and Services exist independently</li> | |
</ul> | |
</section> | |
</section> | |
<section> | |
<section data-transition="linear" id='deployment' data-markdown> | |
### Deployments | |
A `deployment` helps you specify container runtime requirements (in terms of pods) | |
</section> | |
<section> | |
<p>Create a specification for your <code>deployment</code>:</p> | |
<pre><code contenteditable>kubectl run hello-k8s --image=jkleinert/nodejsint-workshop \ | |
--dry-run -o json > deployment.json</code></pre> | |
<p>View the generated deployment spec file:</p> | |
<pre><code contenteditable>cat deployment.json</code></pre> | |
<p>Create a new deployment from your local spec file:</p> | |
<pre><code contenteditable>kubectl create -f deployment.json</code></pre> | |
</section> | |
<section> | |
<p>Create a <code>Service</code> spec to direct traffic:</p> | |
<pre><code contenteditable>kubectl expose deploy/hello-k8s --type=NodePort --port=8080 --dry-run -o json > service.json</code></pre> | |
<p>View the resulting spec file:</p> | |
<pre><code contenteditable>cat service.json</code></pre> | |
<br/> | |
<p>Create a new service from your local spec file:</p> | |
<pre><code contenteditable>kubectl create -f service.json</code></pre> | |
</section> | |
<section> | |
<p>List multiple resources by type:</p> | |
<pre><code contenteditable>kubectl get po,svc,deploy</code></pre> | |
<p>Connect to your new deployment via the associated service port:</p> | |
<pre><code contenteditable>curl $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
</section> | |
<section id='replication'> | |
<h2>Replication</h2> | |
<p>Scale up the <code>hello-k8s</code> deployment to 3 replicas:</p> | |
<pre><code contenteditable>kubectl scale deploy/hello-k8s --replicas=3</code></pre> | |
<p>List pods:</p> | |
<pre><code contenteditable>kubectl get po</code></pre> | |
</section> | |
<section> | |
<p>Edit <code>deploy/hello-k8s</code>, setting <code>spec.replicas</code> to <code>5</code>:</p> | |
<pre><code contenteditable>export EDITOR=nano | |
kubectl edit deploy/hello-k8s -o json</code></pre> | |
<p>Save and quit. What happens?</p> | |
<pre><code contenteditable>kubectl get pods</code></pre> | |
</section> | |
<section id='autorecovery'> | |
<h2>AutoRecovery</h2> | |
<p>Watch for changes to <code>pod</code> resources:</p> | |
<pre><code contenteditable>kubectl get pods --watch &</code></pre> | |
<p>In another terminal, delete several pods by id:</p> | |
<pre><code contenteditable>kubectl delete pod $(kubectl get pods | grep ^hello-k8s | cut -f1 -s -d' ' | head -n 3 | tr '\n' ' ')</code></pre> | |
<p class='fragment'>What happened? How many pods remain?</p> | |
<pre class='fragment'><code contenteditable>kubectl get pods</code></pre> | |
<p class='fragment'>Close your backgrounded <code>--watch</code> processes by running <code>fg</code>, then sending a break signal (<code>CTRL-c</code>)</p> | |
</section> | |
<section> | |
<h3>Observations:</h3> | |
<ul> | |
<li class='fragment'>Use the <code>--dry-run</code> flag to generate new resource specifications</li> | |
<li class='fragment'>A deployment spec contains a pod spec in it's "template" element </li> | |
<li class='fragment'>The API provides <code>edit</code> and <code>watch</code> operations (in addition to <code>get</code>, <code>set</code>, and <code>list</code>)</li> | |
</ul> | |
</section> | |
</section> | |
<section> | |
<section data-transition="linear" id='rs' data-markdown> | |
### ReplicaSets | |
A `replicaset` provides replication and lifecycle management for a specific image release | |
</section> | |
<section> | |
<p>View the current state of your deployment:</p> | |
<pre><code contenteditable>curl $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<p>Watch deployments:</p> | |
<pre><code contenteditable>kubectl get deploy -w &</code></pre> | |
</section> | |
<section> | |
<h3>Rollouts</h3> | |
<p>Update your deployment's image spec to rollout a new release:</p> | |
<pre><code contenteditable>kubectl set image deploy/hello-k8s hello-k8s=jkleinert/nodejsint-workshop:v1</code></pre> | |
<p>View the current state of your deployment</p> | |
<pre><code contenteditable>curl $WORKLOAD_SERVER:$(kubectl get svc/hello-k8s -o jsonpath={.spec.ports[0].nodePort})</code></pre> | |
<p>Ask the API to list <code>replicaSets</code></p> | |
<pre><code contenteditable>kubectl get rs</code></pre> | |
</section> | |
<section> | |
<h3>Rollbacks</h3> | |
<p>View the list of previous rollouts:</p> | |
<pre><code contenteditable>kubectl rollout history deploy/hello-k8s</code></pre> | |
<p>Rollback to the previous state:</p> | |
<pre><code contenteditable>kubectl rollout undo deployment hello-k8s</code></pre> | |
<p>Reload your browser to view the state of your deployment</p> | |
</section> | |
<section> | |
<h3>Cleanup</h3> | |
<p>Cleanup all resources:</p> | |
<pre><code contenteditable>kubectl delete service,deployment hello-k8s</code></pre> | |
<p>Close your remaining <code>--watch</code> listeners by running <code>fg</code> before sending a break signal (<code>CTRL-c</code>)</p> | |
<br/> | |
<p>Verify that your namespace is clean:</p> | |
<pre><code contenteditable>kubectl get all</code></pre> | |
</section> | |
<section> | |
<h3>Observations:</h3> | |
<ul> | |
<li class='fragment'>ReplicaSets provide lifecycle management for pod resources</li> | |
<li class='fragment'>Deployments create ReplicaSets to manage pod replication per rollout (per change in podspec: image:tag, environment vars)</li> | |
<li class='fragment'><code>Deployments</code> > <code>ReplicaSets</code> > <code>Pods</code></li> | |
</ul> | |
</section> | |
<section id='from-kubecon'> | |
<h3>From KubeCon NA 2017</h3> | |
<p><a href="http://bit.ly/kubecon-dev"><img style='width:75%' src="https://i.imgur.com/znrD3yq.jpg" alt="Developing Locally with Kubernetes" title="from KubeCon / CloudNativeCon North America 2017"></a></p> | |
<p>"<a href="http://bit.ly/kubecon-dev">Developing locally with Minikube</a>": <a href="http://youtu.be/_W6O_pfA00s">youtu.be/_W6O_pfA00s</a></p> | |
</section> | |
</section> | |
<section data-transition="convex" id='hands-on-with-openshift'> | |
<h1>Hands-On with OpenShift</h1> | |
</section> | |
<!-- | |
<section id='starter-guide' data-markdown> | |
## OpenShift Starter Lab | |
Self-paced learning material: | |
http://openshift-lab.apps.nofluff-13cb.openshiftworkshop.com | |
</section> | |
--> | |
<section> | |
<section id='build'> | |
<h1>Build</h1> | |
<p class='fragment'>Build and deploy container images</p> | |
</section> | |
<!-- | |
<section id='openshift-web-console'> | |
<h3>Introducing…</h3> | |
<h2>The OpenShift Web Console</h2> | |
<br/> | |
<div class='fragment'> | |
<p>Access the OpenShift Web Console at:</p> | |
<p><a href="https://master.nofluff-13cb.openshiftworkshop.com">https://master.nofluff-13cb.openshiftworkshop.com</a></p> | |
</div> | |
</section> | |
--> | |
<section id='openshift-web-console'> | |
<h3>Introducing…</h3> | |
<h2>The OpenShift Web Console</h2> | |
<br/> | |
<div class='fragment'> | |
<p>Access the OpenShift Web Console by running:</p> | |
<pre><code contenteditable>minishift dashboard</code></pre> | |
</div> | |
</section> | |
<section id='add-to-project'> | |
<h2>Web Workflow: Create</h2> | |
<p>For this example, we will deploy a fork of the <code>ryanj/http-base</code> repo by clicking on "<b>Add to Project</b>" in the web console</p> | |
<p>Example repo source: <a href="http://github.com/ryanj/http-base/">http://github.com/ryanj/http-base</a></p> | |
<ol> | |
<li class='fragment'>Fork the <code>ryanj/http-base</code> repo on GitHub. This will allow you to configure your own GitHub webhooks in the upcoming <a href='#/deploy'>Deploy</a> section</li> | |
<li class='fragment'>Return to the web console and click on "Add to Project"</li> | |
<li class='fragment'>Next, select a <code>nodejs</code> base image, and name your webservice "<code>http-base</code>". Then enter the github url for <b>your</b> fork</li> | |
<li class='fragment'>Review the options, then press the "<b>Create</b>" button when you're ready to proceed</li> | |
</ol> | |
</section> | |
<section id='get-pods'> | |
<h2>Container Status</h2> | |
<p class='fragment'>The web console uses a socket stream to report status changes as they occur throughout the cluster</p> | |
<div class='fragment'> | |
<p>After the build task has completed, find the <code>NAME</code> of the pod where your image has been deployed:</p> | |
<pre><code contenteditable>oc get pods</code></pre> | |
</div> | |
<div class='fragment'> | |
<p>As with the core APIs, the CLI output is consistently formatted, following established patterns:</p> | |
<pre><code contenteditable>kubectl get pods</code></pre> | |
</div> | |
</section> | |
<section id='source-to-image-demo'> | |
<h2>Source</h2> | |
<p>to</p> | |
<h1>Image</h1> | |
<p class='fragment'>Combines source repos and operationally-maintained builder images to produce application images</p> | |
<p class='fragment'>Available as a standalone project, for use with Jenkins or other external builder processes: <a href="https://github.com/openshift/source-to-image">github.com/openshift/source-to-image</a></p> | |
</section> | |
</section> | |
<section data-transition="linear"> | |
<section id="automate"> | |
<h1>Automate</h1> | |
<p class='fragment'><code>git push</code> to deploy</p> | |
</section> | |
<section id='clone'> | |
<h3>Send in the Clones</h3> | |
<p>Clone a local copy of <b>your repo fork</b> by adding <b>your own github username</b> to the following command:</p> | |
<pre><code>git clone http://github.com/YOUR_GH_USERNAME/http-base | |
cd http-base</code></pre> | |
</section> | |
<section id='webhooks'> | |
<h2>WebHook Build Automation</h2> | |
<p>Set up a commit WebHook to automate image production</p> | |
<p class='fragment'>Explore the <code>Build</code> resources using the web console. Look for the GitHub Webhook settings. Copy the webhook url, and paste it into your repo's Webhook settings on GitHub</p> | |
<p class='fragment'>If you're running OpenShift locally in a VM, try using <a href="http://www.ultrahook.com/">ultrahook</a> to proxy webhook events to your laptop</p> | |
</section> | |
<section id='git-push-to-build-and-ship'> | |
<h2>ReBuild on Push</h2> | |
<p class='fragment'>After configuring the webhook for your repo, add a small commit locally, then <code>git push</code> to deploy</p> | |
<pre class='fragment'><code contenteditable>git push</code></pre> | |
<p class='fragment'>Or, use GitHub's web-based editor to make a minor change</p> | |
<div class='fragment'><p>If you don't have a working webhook to automate the build process, it can also be started manually:</p> | |
<pre><code contenteditable>oc start-build http-base</code></pre> | |
</div> | |
</section> | |
<section id="deployment-strategies"> | |
<h2>Deployment Strategies</h2> | |
<p class="fragment">Get more control of your container rollout and update processes by selecting appropriate <a href="https://docs.openshift.org/latest/dev_guide/deployments.html#strategies">deployment strategies</a> for your fleet of managed containers</p> | |
</section> | |
</section> | |
<section data-transition="linear"> | |
<section id="iterate"> | |
<h1>Iterate</h1> | |
<p class='fragment'>Rapid iteration with a fully containerized toolchain</p> | |
</section> | |
<section id='oc-rsync'> | |
<h3>Live Development</h3> | |
<p class='fragment'>Make a minor edit to your local repo's <code>index.html</code> file,</p> | |
<div class='fragment'> | |
<p>then test your changes <i>before you commit</i> by synching content into your hosted container:</p> | |
<pre><code contenteditable>export PODNAME=$(oc get pods -l app=http-base | tail -n 1 | cut -f1 -d' ') | |
oc rsync -w --exclude='.git,node_modules' . $PODNAME:</code></pre> | |
</div> | |
</section> | |
<section id="terminal" data-markdown> | |
## Terminal Access | |
* Available in the Web Console | |
* And on the CLI, with: | |
oc exec -it $PODNAME -- bash | |
* Verify in-cluster DNS routing for named services | |
curl http-base | |
</section> | |
<section id='keys-and-configs' data-markdown> | |
## Configuration | |
[Environment Variables](https://docs.openshift.org/latest/dev_guide/environment_variables.html) are one way to add configuration settings to your images: | |
oc env dc/http-base KEY=VALUE | |
ConfigMaps and Secrets are also useful configuration abstractions | |
</section> | |
<section id='logging' data-markdown> | |
## Logs | |
Centralized logging and metrics | |
</section> | |
</section> | |
<section> | |
<section id='collaborate'> | |
<h1>Collaborate</h1> | |
<p class='fragment'>Share solutions and replicate your success</p> | |
</section> | |
<section id='service-catalog-on-openshift'> | |
<h2>Service Catalog & Brokers</h2> | |
<p>Expose and provision services</p> | |
<p><img style='width:100%;' src='https://github.com/ryanj/redisconf-2018/raw/master/static/catalog-options.png' alt='pluggable-broker-options' /></p> | |
<p><a href='https://www.openservicebrokerapi.org/'>www.openservicebrokerapi.org</a> | |
</section> | |
<section data-markdown> | |
### Available Service Brokers | |
* [OpenShift Template Broker](https://docs.openshift.com/container-platform/3.6/architecture/service_catalog/template_service_broker.html) | |
* [AnsibleServiceBroker / AutomationBroker](http://automationbroker.io/) | |
* [Helm Chart Broker](https://github.com/google/helm-broker) | |
* [AWS Broker](https://aws.amazon.com/blogs/opensource/aws-service-operator-kubernetes-available/) | |
* [DIY MariaDB Broker example](https://github.com/prydonius/mariadb-broker) | |
* [CloudFoundry Spring Cloud](https://github.com/spring-cloud/spring-cloud-open-service-broker) | |
* [Kubernetes Service Catalog](https://github.com/kubernetes-incubator/service-catalog) | |
</section> | |
<section id='service-catalog' data-markdown> | |
### Everyone's Service Catalog | |
> "The Open Service Broker API project allows developers, ISVs, and SaaS vendors a single, simple, and elegant way to deliver services to applications running within cloud native platforms" | |
Works with: [Kubernetes](https://github.com/kubernetes-incubator/service-catalog), [OpenShift](https://docs.openshift.com/container-platform/3.6/architecture/service_catalog/template_service_broker.html), [Cloud Foundry](https://github.com/spring-cloud/spring-cloud-open-service-broker) | |
</section> | |
<section id='installers'> | |
<h2>Templates as Installers</h2> | |
<div class='fragment'> | |
<p>Install a template into the current project, making it easier to reuse:</p> | |
<pre><code contenteditable>oc create -f template.json</code></pre> | |
</div> | |
<div class='fragment'> | |
<p>Create an application from an installed template, from a file, or from a url:</p> | |
<pre><code contenteditable>oc new-app -f template.json</code></pre> | |
</div> | |
</section> | |
<section id='composable-app-example'> | |
<h2>Multi-Service App Example</h2> | |
<p>Nodejs and MongoDB multi-service application example:</p> | |
<pre><code contenteditable>oc create -f https://raw.githubusercontent.com/openshift-roadshow/nationalparks-js/master/nationalparks-js.json</code></pre> | |
<p><a href="https://raw.githubusercontent.com/openshift-roadshow/nationalparks-js/master/nationalparks-js.json">github.com/ryanj/nationalparks-js</a></p> | |
<p>Review and install the above template content using <code>oc create</code>, then try launching it via the web-based Service Catalog.</p> | |
<div class="fragment"> | |
<p>When you're done, list all available API resources to review the contents of your project namespace:</p> | |
<pre><code contenteditable>oc get all</code></pre> | |
</div> | |
</section> | |
</section> | |
<section> | |
<section id='extensibility'> | |
<h1>Advanced Extensibility</h1> | |
</section> | |
<section id='standardize' data-markdown> | |
### Standardize your environments with custom base images | |
https://docs.okd.io/latest/using_images/s2i_images/nodejs.html | |
https://github.com/bucharest-gold/centos7-s2i-nodejs | |
<!-- TODO: include node10 installation notes --> | |
</section> | |
<section id='Operators'> | |
<h1>"Operators"</h1> | |
<p><a href="https://coreos.com/operators/">coreos.com/operators</a></p> | |
<p class='fragment'>Operators = <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">Custom Resources</a> + <a href="https://github.com/kubernetes/sample-controller">Custom Controllers</a></p> | |
<p class='fragment'><a href="https://github.com/operator-framework/awesome-operators">github.com/operator-framework/awesome-operators</a></p> | |
</section> | |
</section> | |
<section> | |
<section id='wrap-up'> | |
<h1>Wrap Up</h1> | |
</section> | |
<section id='exit-survey'> | |
<h3>Exit Survey</h3> | |
<ol> | |
<li class='fragment'>have experience using containers?</li> | |
<li class='fragment'>have experience using Kubernetes?</li> | |
<li class='fragment'>Do you consider yourself to be basically proficient with the <code>oc</code> or <code>kubectl</code> command-line tools?</li> | |
<li class='fragment'>Can you name five basic Kubernetes primitives or resource types?</li> | |
<li class='fragment'>Ready to start standardizing your web development processes with containers?</li> | |
</ol> | |
</section> | |
</section> | |
<section> | |
<section data-markdown> | |
## Resources | |
</section> | |
<section data-markdown> | |
### Kubernetes SIGs | |
[Kubernetes Special Interest Groups (SIGs)](https://github.com/kubernetes/community/blob/master/sig-list.md) | |
</section> | |
<section id='ebook'> | |
<h3>Free O'Reilly Ebook</h3> | |
<p><a href="https://www.openshift.com/deploying-to-openshift/"><img src="https://www.openshift.com/hubfs/images/openshift-legacy/promotions/deploying-to-openshift/deploying-to-openshift.png" style="margin-left:auto;margin-right:auto;width:45%;display:block;box-shadow:none;align:center;"></a></p> | |
<p><a href="https://www.openshift.com/deploying-to-openshift/">Deploying to OpenShift</a></p> | |
</section> | |
<section id='learn-more' data-markdown> | |
### More Opportunities to Learn | |
* Today's workshop: [github.com/openshift-labs/starter-guides](https://github.com/openshift-labs/starter-guides) | |
* Cloud-Native Java: [github.com/openshift-labs/cloud-native-guides](https://github.com/openshift-labs/cloud-native-guides) | |
* OpenShift Learning Portal: [learn.openshift.com](http://learn.openshift.com) | |
</section> | |
<section id='js-tools' data-markdown> | |
## Platform Tools for JS developers | |
* https://www.npmjs.com/package/openshift-rest-client | |
* https://www.npmjs.com/package/nodeshift | |
* https://github.com/bucharest-gold/centos7-s2i-nodejs | |
</section> | |
<section data-transition="linear" id='kubernetes-terminology' data-markdown> | |
## K8s Terminology | |
1. [node](https://kubernetes.io/docs/concepts/architecture/nodes/) | |
2. [pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/) | |
3. [deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) | |
4. [service](https://kubernetes.io/docs/concepts/services-networking/service/) | |
5. [replicaSet (rs)](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/) | |
</section> | |
<section data-transition="linear" id='openshift-terminology' data-markdown> | |
## OpenShift Terminology | |
1. [buildConfig (bc)](https://docs.openshift.org/latest/rest_api/apis-build.openshift.io/v1.BuildConfig.html) | |
2. [imageStream (is)](https://docs.openshift.org/latest/rest_api/apis-image.openshift.io/v1.ImageStream.html) | |
3. [deploymentConfig (dc)](https://docs.openshift.org/latest/rest_api/apis-apps.openshift.io/v1.DeploymentConfig.html) | |
4. [route](https://docs.openshift.org/latest/rest_api/apis-route.openshift.io/v1.Route.html) | |
5. [template](https://docs.openshift.org/latest/rest_api/oapi/v1.Template.html) | |
</section> | |
</section> | |
<section id='try-openshift' data-markdown> | |
### More Ways to Try OpenShift | |
* [OpenShift Origin](https://github.com/openshift/origin) (and [minishift](https://github.com/minishift/minishift)) | |
* [OpenShift Learning Portal](http://learn.openshift.com) | |
* [OpenShift Online (Starter and Pro plans available)](https://www.openshift.com/products/online/) | |
* [OpenShift Dedicated (operated on AWS, GCE, and Azure)](https://www.openshift.com/products/dedicated/) | |
* [OpenShift Container Platform (supported on RHEL, CoreOS)](https://www.openshift.com/products/container-platform/) | |
</section> | |
<section data-background-color='black' id='q-and-a'> | |
<h1 style='color:white;'>Q&A</h1> | |
</section> | |
<section style="padding-top:10px;position:relative;bottom:2%;" data-background="https://gist.githubusercontent.com/ryanj/9181e48c0dd8e6b45d692a11d5a72bd5/raw/9892bcb34a809c25c24c43a2ea33bb69b7684f0b/node-js-interactive-1.jpg" data-background-color="black" data-background-size="cover" data-background-position="top" id='thank-you'> | |
<h1 style='color:white;margin-bottom:0px;bottom:25px;position: relative;' class="fragment grow">Thank You!</h1> | |
<p style='color:white;margin-bottom:0px;margin-top:0px;'><img style="margin:0px;width:63%;position: relative;bottom: 2%;" alt="RyanJ" src="https://tek.phparch.com/wp-content/uploads/sites/7/2018/05/ryan-jarvinen-headshot-e1525184794614-531x424.jpg" /></p> | |
<p style='margin-top:0px;color:white;'><a href="https://twitter.com/ryanj">@RyanJ / ryanj@redhat.com</a></p> | |
<h3 class="fragment grow" style='color:white;text-transform:none;'><a href="http://gist-reveal.it/a42cf467af9557c5d8a49bc45a7bd515?theme=4f6d86229cc5ee8c07f5a70c1e3fcc41">bit.ly/nfjs-k8s</a></h3> | |
</section> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment