Using our convenient web app, you get the benefit of a warm, delicious pie without having to even call your order in. Order in your pajamas, order 24 hours a day, order on Christmas. We're here to serve you.
No one has time to drive somewhere to pick up a pie, let alone bake one yourself. With our same day delivery service, you'll be serving up a slice of pie to your guests before you can say boysenberry.
We offer special deals for first time customers and group orders. With our discounts, you'll get pies at a fraction of the cost you would pay at your local bakery or grocery store.
A pie bird is a tool, usually made of ceramic and shaped like a bird, that aids in venting steam from your double-crusted pie effectively preventing boil overs from juicy fillings and helping you attain a nice, crisp crust. They are also sometimes referred to as pie vents, pie chimneys, pie whistles and pie funnels. Whether bird-shaped or not, they are usually tall, narrow and always hollow.


Please visit https://github.com/GreenFour/pencil-knight/blob/master/README.md for instructions!
***PRESS ENTER TO PAUSE AND VIEW CONTROLS***


To create an index, the syntax is similar to creating a table:
indextest=# h CREATE INDEX
Command: CREATE INDEX
Description: define a new index
Syntax:
CREATE [ UNIQUE ] INDEX index_name ON table
[ USING acc_method ] ( column [ ops_name ] [, ...] )
[ WHERE predicate ]
CREATE [ UNIQUE ] INDEX index_name ON table
[ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] )
[ WHERE predicate ]
Let's create an index on the id column:
pgsql@elrond ~$ time echo "CREATE INDEX id_index ON indextest(id)" | psql -d indextest
CREATE INDEX
real 2m31.495s
user 0m0.030s
sys 0m0.030s
It took us around two and one half minutes to create the index.
When we create an index, it must be given a name. This is mainly so we can see at a glance with the di command in psql what
indexes we have on what particular tables:
indextest=# di
List of relations
Schema | Name | Type | Owner | Table
--------+----------+-------+-------+-----------
public | id_index | index | pgsql | indextest
(1 row)
To display what columns the index was created on, use the d <tablename> command:
indextest=# d indextest
Table "public.indextest"
Column | Type | Modifiers
--------+--------------+-----------
id | integer |
name | text |
number | integer |
letter | character(1) |
Indexes: id_index btree (id)
As we can see, the index is called id_index and it was created on the id column.
Let's check the speed again:
pgsql@elrond ~$ time echo "SELECT * FROM indextest WHERE id='50000'" | psql -d indextest
id | name | number | letter
-------+----------+----------+--------
50000 | WvMw5oZN | 53641070 | d
(1 row)
real 0m0.344s
user 0m0.030s
sys 0m0.020s
It has gone from 30 seconds to around a third of a second! That's a great performance improvement just by creating a single
index.
Removing Indexes
Removing indexes can speed up updating or adding data to tables. Since an index can't be altered (you can't add another column to
or remove a column from an index), to change an index it has to be removed then re-added. Dropping an index is quite easy:
indextest=# DROP INDEX id_index;
|


Battery Voltage. The returned value represents the difference between the battery voltage and V_BG (1.23V). The formula to convert it to mV is: 1223 * 1024 / value.


range
is a new argument for reading a rectangular range, possibly open. (#314, #8)
n_max
is a new argument that limits the number of data rows read. (#306, #281)
Empty cells, rows, columns (xlsx #248 and #240, xls #271): Cells with no content are no longer loaded, even if they appear in the file. Affects cells that have no data but that carry explicit formatting, detectable in Excel as seemingly empty cells with a format other than “General”. Such cells may still exist in the returned tibble, with value NA
, depending on the sheet geometry.
NA
.User-supplied col_names
are processed relative to user-supplied col_types
, if given. Specifically, col_names
is considered valid if it has the same length as col_types
, before or after removing skipped columns. (#81, #261)
"list"
is a new accepted value for col_types
. Loads data as a list of length-1 vectors, that are typed using the logic from col_types = NULL
, but on a cell-by-cell basis (#262 @gergness).
"logical"
is a new accepted value for col_types
. When col_types = NULL
, it is the guessed type for cells Excel advertises as Boolean. When a column has no data, it is now filled with logical NA
. (#277, #270)
"guess"
is a new accepted value for col_types
. Allows the user to specify some column types, while allowing others to be guessed (#286)
A user-specified col_types
of length one will be replicated to have length equal to the number of columns. (#127, #114, #261)
"blank"
has been deprecated in favor of the more descriptive and readr-compatible "skip"
, which is now the preferred way to request that a column be skipped. (#260, #193, #261)
guess_max
is a new argument that lets user adjust the number of rows used to guess column types. (#223, #257 @tklebel and @jennybc)
trim_ws
is a new argument to remove leading and trailing whitespace. It defaults to TRUE
. (#326, #211)
na
can now hold multiple NA values, e.g., read_excel("missing-values.xls", na = c("NA", "1"))
. (#13, #56, @jmarshallnz)
Coercions and cell data:
NA
instead of their integer representation. Throws warning. (#277, #263)read_excel()
attempts to coerce the string to numeric and falls back to NA
if unsuccessful. Throws warning. (#277, #217, #106)NA
(instead of the string "error"
). (#277, #62)"Unknown type: 517"
. (#274, #259)Dates in .xlsx files saved with LibreOffice are now recognized as such. (#134, @zeehio)
Many 3rd party tools write xls and xlsx that comply with the spec, but that are quite different from files produced by Excel.
Namespace prefixes are now stripped from element names and attributes when parsing XML from xlsx. Workaround for the creative approach taken in some other s/w, coupled with rapidxml’s lack of namespace support. (#295, #268, #202, #80)
Excel mixes 0- and 1-indexing in reported row and column dimensions for xls and libxls expects that. Other s/w may index from 0 for both, preventing libxls from reading the last column. Patched to restore access to those cells. (#273, #180, #152, #99)
More robust logic for sheet lookup in xlsx. Improves compatibility with xlsx written by a variety of tools and/or xlsx containing chartsheets. (#233, #104, #200, #168, #116, @jimhester and @jennybc)
The numFmtId
attribute is no longer accessed when it does not exist (xlsx written by http://epplus.codeplex.com). (#191, #229)
Location is inferred for cells that do not declare their location (xlsx written by JMP). (#240, #163, #102)
read_xls()
and read_xlsx()
are now exposed, such that files without an .xls
or .xlsx
extension can be read. (#85, @jirkalewandowski)
The Lotus 1-2-3 leap year bug is now accounted for, i.e. date-times prior to March 1, 1900 import correctly. Date-times on the non-existent leap day February 29, 1900 import as NA and throw a warning. (#264, #148, #292)
The tibble package is now imported (#175, @krlmlr) and tibble::repair_names()
is used to prevent empty, NA
, or duplicated column names. (#216, #208, #199 #182, #53, #247).
Default column names for xlsx now start with X__1 instead of X__0. (#98, @zeehio, @krlmlr)
Fix compilation warnings/failures (FreeBSD 10.3 #221, gcc 4.9.3 #124) and/or problems reading xls (CentOS 6.6 #189). (#244, #245, #246 @jeroen)
Unwanted printed output (e.g., DEFINEDNAME: 21 00 00 ...
) is suppressed when reading xls that contains a defined range. (#82, #188, @PedramNavid)


This Igor uses the Certificate Authority of another Igor, located at {{pluginData.ca}}.
{% else %}This Igor serves as its own certificate authority.
{% endif %}To change this, add an entry ca to /data/plugindata/{{pluginName}} (or remove that entry) and reboot igor.
The CA uses Distinghuished Name {{pluginObject.dn()}} and the current status is {{pluginObject.status()}}.
To trust certificates signed by this Igor CA, download the root certificate chain and install in your browser or system.
If available, the Certificate Revocation List can be downloaded too.
Here is the list of all certificates ever created by this CA:
{% set allCertificates = pluginObject.list(callerToken=callerToken) %}| Serial | Canonical Name | Distinghuished Name | Status | Valid until | OP |
|---|---|---|---|---|---|
| {{item.serial}} | {{item.dn.CN}} |
{% for k,v in item.dn.items() %}
{{k}}={{v}} {% endfor %} |
{{item.status}} | {{item.revoked or item.expires}} | {% if item.status == 'valid' %} {% endif %} |
Create a key and CSR (Certificate Signing Request) locally, for example using the igorCA csr command.
Enter the CSR (PEM encoded) in the following field and submit:
The result is the (PEM-encoded) certificate you can use for your service (together with the key from the igorCA csr step).
Alternatively you can use igorCA gen to have igorCA submit the CSR here (so you don't have to copy and paste it).
Another alternative is to have the Igor CA module do all of the steps above for you: create a key, create a CSR and sign it. You will get the private key and certificate as a result (encoded in DER form and concatenated).
Note that Igor does not keep a copy of the key, it is only returned to you (and used to sign the CSR), but the key is transmitted back to you over the internet so if you are very security-conscious you should use one of the other methods to create the key and certificate. You must keep the key safe once you have received it.


{% import 'macros/form.html' as form %} {# this snippet is meant to be called from organization.html, not used as a form_snippet directly #} {% macro _organization() %} {% set existing_org = data.owner_org or data.group_id %} {% call form.input_block('field-organizations', label=h.scheming_language_text(field.label), error=errors[field.field_name], is_required=org_required, classes=['form-group', 'control-medium'], extra_html=caller() if caller, ) %}


Location map
- Today
- Yesterday
- Two days ago
- Other
- Very last track
- Last track
- Long track
- Very long track
- Give me all the track!
Loading map...
Statistics
Device configuration
Device name
Authentication
Firebase sign-in status: Unknown
Firebase auth
currentUser
object value:null


Caoimhin Kelleher Looks Could Leave Liverpool For Championship Loan Move
by Robert RedmondCaoimhin Kelleher could secure a loan move away from Liverpool in January.
The Republic of Ireland Under-21 goalkeeper was part of the Liverpool squad that won the Champions League in June and has played for Jurgen Klopp’s team in the EFL Cup this season, making two appearances. He impressed on his senior debut in the victory over MK Dons in September, where he kept a clean sheet.
In October, he conceded five goals in the ridiculous 5-5 draw against Arsenal at Anfield. But Kelleher made up for some shaky moments earlier in the match by saving in the penalty shootout victory.
The Cork-native will be between the sticks again for Liverpool in the EFL Cup quarter-final against Aston Villa in December. However, he could then be set for a spell away from the club in the new year.
According to The Liverpool Echo, Preston North End are interested in taking Kelleher on loan. The report claims that Liverpool will only sanction a move for the Irish goalkeeper if the loan club can guarantee that he will feature in the first-team.
Preston are currently third in the Championship and Declan Rudd is their starting ‘keeper.
Rhian Brewster also looks set for a loan move away from Anfield. The England underage forward is wanted by Aston Villa, Crystal Palace and Swansea City, amongst others.
Kelleher is behind Alisson Becker and Adrian in the pecking order for Liverpool but is highly rated by club and country. The 21-year-old is a modern goalkeeper, as comfortable with his feet as he is making saves.
A spell away from Anfield, playing first-team football, would surely greatly benefit him at this stage of his career.


ALT + MToggle menu bar ALT + TToggle theme ALT + IIncrease font size ALT + ODecrease font size ALT + 0Reset font size ALT + GToggle gutter ALT + WToggle word wrap ALT + LToggle log window
The Canvas Animation Playground is an experimental tool for creating HTML5 canvas animations. Just type in JavaScript code and it will be interpreted for the <canvas> box on the right side. The function "CanvasAnimationLoop()" gets called on every browser frame via "requestAnimationFrame". Animations can be exported as stand-alone HTML files.
There are some global objects and functions you may want to use:
CVS = <canvas> element itself
CTX = canvas.getContext("2d")
LOG(message) = send message to log window
CLEARLOG() = clears log window
Click on the gutter to fold an object or a function in and out.
The tool makes use of HTML5's localStorage and has been optimized for modern browsers. Internet Explorer is supported from version 9 on.
The project was developed by Matthias Schuetz and is available on GitHub. It's based on HTMLiveCode, a real-time code editor.


16 trapped in China coal mine blast
by Agency ReportLocal authorities on Monday reported that sixteen people were trapped in a coal mine gas explosion in north China’s Shanxi Province.
A preliminary investigation showed that 34 miners were working underground at the coal mine in Pingyao County when the blast happened at 1:50 p.m. on Monday.
According to the Shanxi Administration of Coal Mine Safety, so far, 18 people were saved and five others were sent to the hospital.
Authorities said rescue work and further investigation are underway.
(Xinhua/NAN)


Mourinho aims to find out what is in Eriksen’s ‘mind or heart’
The new Tottenham boss left the Danish midfielder on the bench when he named his team to face West Ham.
Jose Mourinho said he needs to find out what is in Christian Eriksen’s “mind or heart” after omitting him from his first Tottenham starting line-up.
Mourinho, appointed as Spurs boss on Wednesday after Mauricio Pochettino was sacked, takes charge of his first game in the lunch-time kick-off at London rivals West Ham.
Denmark international Eriksen, out of contract next year, was expected to leave Spurs during the summer and has started only eight games this season.
“The match today is very important, but our future is also very important and I need to understand really what is in Christian’s mind or heart,” Mourinho told BT Sport.
“We have to make the right decision for the club. But on top of that I believe that Lucas (Moura) and Sonny (Son Heung-min) and Harry Kane – they are players with a good understanding.
“We’re going to try and find, in the little time we’ve had to work, some positions and some dynamic where they can hurt the opposition and let’s see if we can do it.
“I like the ideas, but I like to work the ideas and this today is more about ideas than to work them, so let’s see if we can find happiness.”


Configuraton like ~/.aws/config file
(support color = RRGGBB)
#
About ~/.aws/config file
Save
Read me


We aggregate solutions!
- seize integrated communities
- extend B2B e-tailers
- exploit distributed vortals
- whiteboard granular infomediaries
- engage mission-critical e-markets
- mesh sticky paradigms
- grow back-end synergies
- matrix extensible platforms
- implement cross-media partnerships
- monetize open-source systems
- redefine revolutionary e-tailers
- productize dot-com bandwidth
- matrix one-to-one eyeballs
- unleash magnetic vortals
- incubate best-of-breed content


Iterator Result
The iterator protocol defines a standard
way to produce a sequence of values. An object is an iterator when it implements a next()
method which returns objects
that have at least the two properties value
and done
. This module provides a base class for such iterator results.
Installation
npm install iterator-result
Usage
import IteratorResult from "iterator-result";
export class InfiniteIterator {
constructor() {
this.result = new IteratorResult();
}
next() {
return this.result;
}
}
Contributing
Maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.


.. to CHM examples!
HTMLHelp is the current help system for Microsoft Windows. This file includes some examples how to use Microsoft HTMLHelp and is used to show how to work with HTMLHelp 1.x CHM files in Visual Basic Applications.
This "Welcome" page is the default page of the compiled help module (CHM).
Version Information:
Release: 2005-07-17
(c) help-info.de


class: center, middle, inverse, title-slide # Preamble ## SISBID 2018
https://github.com/dicook/SISBID
### Di Cook (
<EMAIL_ADDRESS>,
@visnut
)
Heike Hofmann (
<EMAIL_ADDRESS>,
@heike_hh
) ### 07/25-27/2018 --- ![](Hello-Di.png) --- ![](Hello-Heike.png) --- class: inverse middle # Your turn: meet your neighbors! Take a couple of minutes to introduce yourself to the people around you. Ask them some questions about where they come from, why they chose to do the workshop, what sort of data they usually work with, whether they have used R before, .... ``` 😄 ✋ 🖖 💻 ``` --- # Getting up and running - R and RStudio: *If R were an airplane, RStudio would be the airport* [Julie Lowndes](http://jules32.github.io/resources/RStudio_intro/) - Projects: Like a drawer in your filing cabinet for organising your work - RMarkdown: reproducible reports, presentations, web sites, web apps, books - Web site: https://github.com/dicook/SISBID --- class: inverse middle # Your turn: Getting started - __Download a zip archive__ of the workshop files from [github site](https://github.com/dicook/SISBID) contains all the markdown files, code files, and data for this workshop. __Unzip the file__ and __rename__ it `SISBID` - __Double-click SISBID.Rproj:__ the R project helps organise your work over these next few days. ![](projectname.png) --- # What is R Markdown? - __Get started with RMarkdown__, by creating a new R notebook. Knit it. - Now __open the file that created these slides__ `0-preamble/index.Rmd`. Navigate to the code chunks at the bottom of the file and run them. You should be able to run these one chunk at a time. If you want to compile the entire document, you will need to install several new packages. --- # Installing packages The three main locations that we will draw packages from are CRAN, Bioconductor and github. From CRAN ```r install.packages("ggenealogy") ``` From bioconductor ```r source("https://bioconductor.org/biocLite.R") biocLite("plyranges") ``` From github repos ```r devtools::install_github("heike/gglogo") devtools::install_github("dicook/nullabor") ``` --- # Resources - [RStudio cheatsheets](https://www.rstudio.com/resources/cheatsheets/) - Q/A site: [http://stackoverflow.com](http://stackoverflow.com) - Dynamic Documents with R and knitr [Yihui Xie](http://yihui.name/knitr/) --- # Share and share alike Materials for this workshop build on slides provided by several people, but primarily Hadley Wickham and Carson Sievert. This work is licensed under the Creative Commons Attribution-Noncommercial 3.0 United States License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/3.0/us/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.


Reports methods and fields in
Serializable
and
Externalizable
classes that are suitable to be annotated with
java.io.Serial
annotation.


--- layout: post title: Listing all the Rails books currently available categories: - documentation author: David published: true date: 2006-05-25 02:52:56.000000000 +01:00 ---
We now have a new book list page on the Rails site that contains all the titles that are currently out in print or beta form. We’ll be adding to it as more titles become available.


http://jpetazzo.github.io/2013/10/08/docker-containers-metrics/ .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- class: extra-details ## Application metrics - Arbitrary metrics related to your application and business - System performance: request latency, error rate... - Volume information: number of rows in database, message queue size... - Business data: inventory, items sold, revenue... .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- class: extra-details ## Detecting scrape targets - Prometheus can leverage Kubernetes service discovery (with proper configuration) - Services or pods can be annotated with: - `prometheus.io/scrape: true` to enable scraping - `prometheus.io/port: 9090` to indicate the port number - `prometheus.io/path: /metrics` to indicate the URI (`/metrics` by default) - Prometheus will detect and scrape these (without needing a restart or reload) .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- ## Querying labels - What if we want to get metrics for containers belonging to a pod tagged `worker`? - The cAdvisor exporter does not give us Kubernetes labels - Kubernetes labels are exposed through another exporter - We can see Kubernetes labels through metrics `kube_pod_labels` (each container appears as a time series with constant value of `1`) - Prometheus *kind of* supports "joins" between time series - But only if the names of the tags match exactly .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- ## Unfortunately ... - The cAdvisor exporter uses tag `pod_name` for the name of a pod - The Kubernetes service endpoints exporter uses tag `pod` instead - See [this blog post](https://www.robustperception.io/exposing-the-software-version-to-prometheus) or [this other one](https://www.weave.works/blog/aggregating-pod-resource-cpu-memory-usage-arbitrary-labels-prometheus/) to see how to perform "joins" - Alas, Prometheus cannot "join" time series with different labels (see [Prometheus issue #2204](https://github.com/prometheus/prometheus/issues/2204) for the rationale) - There is a workaround involving relabeling, but it's "not cheap" - see [this comment](https://github.com/prometheus/prometheus/issues/2204#issuecomment-261515520) for an overview - or [this blog post](https://5pi.de/2017/11/09/use-prometheus-vector-matching-to-get-kubernetes-utilization-across-any-pod-label/) for a complete description of the process .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- ## In practice - Grafana is a beautiful (and useful) frontend to display all kinds of graphs - Not everyone needs to know Prometheus, PromQL, Grafana, etc. - But in a team, it is valuable to have at least one person who know them - That person can set up queries and dashboards for the rest of the team - It's a little bit like knowing how to optimize SQL queries, Dockerfiles... Don't panic if you don't know these tools! ...But make sure at least one person in your team is on it 💯 ??? :EN:- Collecting metrics with Prometheus :FR:- Collecter des métriques avec Prometheus .debug[[k8s/prometheus.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/prometheus.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg)] --- name: toc-stateful-sets class: title Stateful sets .nav[ [Previous section](#toc-collecting-metrics-with-prometheus) | [Back to table of contents](#toc-module-8) | [Next section](#toc-running-a-consul-cluster) ] .debug[(automatically generated title slide)] --- # Stateful sets - Stateful sets are a type of resource in the Kubernetes API (like pods, deployments, services...) - They offer mechanisms to deploy scaled stateful applications - At a first glance, they look like *deployments*: - a stateful set defines a pod spec and a number of replicas *R* - it will make sure that *R* copies of the pod are running - that number can be changed while the stateful set is running - updating the pod spec will cause a rolling update to happen - But they also have some significant differences .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Stateful sets unique features - Pods in a stateful set are numbered (from 0 to *R-1*) and ordered - They are started and updated in order (from 0 to *R-1*) - A pod is started (or updated) only when the previous one is ready - They are stopped in reverse order (from *R-1* to 0) - Each pod know its identity (i.e. which number it is in the set) - Each pod can discover the IP address of the others easily - The pods can persist data on attached volumes 🤔 Wait a minute ... Can't we already attach volumes to pods and deployments? .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Revisiting volumes - [Volumes](https://kubernetes.io/docs/concepts/storage/volumes/) are used for many purposes: - sharing data between containers in a pod - exposing configuration information and secrets to containers - accessing storage systems - Let's see examples of the latter usage .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Volumes types - There are many [types of volumes](https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes) available: - public cloud storage (GCEPersistentDisk, AWSElasticBlockStore, AzureDisk...) - private cloud storage (Cinder, VsphereVolume...) - traditional storage systems (NFS, iSCSI, FC...) - distributed storage (Ceph, Glusterfs, Portworx...) - Using a persistent volume requires: - creating the volume out-of-band (outside of the Kubernetes API) - referencing the volume in the pod description, with all its parameters .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Using a cloud volume Here is a pod definition using an AWS EBS volume (that has to be created first): ```yaml apiVersion: v1 kind: Pod metadata: name: pod-using-my-ebs-volume spec: containers: - image: ... name: container-using-my-ebs-volume volumeMounts: - mountPath: /my-ebs name: my-ebs-volume volumes: - name: my-ebs-volume awsElasticBlockStore: volumeID: vol-049df61146c4d7901 fsType: ext4 ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Using an NFS volume Here is another example using a volume on an NFS server: ```yaml apiVersion: v1 kind: Pod metadata: name: pod-using-my-nfs-volume spec: containers: - image: ... name: container-using-my-nfs-volume volumeMounts: - mountPath: /my-nfs name: my-nfs-volume volumes: - name: my-nfs-volume nfs: server: <IP_ADDRESS> path: "/exports/assets" ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Shortcomings of volumes - Their lifecycle (creation, deletion...) is managed outside of the Kubernetes API (we can't just use `kubectl apply/create/delete/...` to manage them) - If a Deployment uses a volume, all replicas end up using the same volume - That volume must then support concurrent access - some volumes do (e.g. NFS servers support multiple read/write access) - some volumes support concurrent reads - some volumes support concurrent access for colocated pods - What we really need is a way for each replica to have its own volume .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Individual volumes - The Pods of a Stateful set can have individual volumes (i.e. in a Stateful set with 3 replicas, there will be 3 volumes) - These volumes can be either: - allocated from a pool of pre-existing volumes (disks, partitions ...) - created dynamically using a storage system - This introduces a bunch of new Kubernetes resource types: Persistent Volumes, Persistent Volume Claims, Storage Classes (and also `volumeClaimTemplates`, that appear within Stateful Set manifests!) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Stateful set recap - A Stateful sets manages a number of identical pods (like a Deployment) - These pods are numbered, and started/upgraded/stopped in a specific order - These pods are aware of their number (e.g., #0 can decide to be the primary, and #1 can be secondary) - These pods can find the IP addresses of the other pods in the set (through a *headless service*) - These pods can each have their own persistent storage (Deployments cannot do that) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-running-a-consul-cluster class: title Running a Consul cluster .nav[ [Previous section](#toc-stateful-sets) | [Back to table of contents](#toc-module-8) | [Next section](#toc-persistent-volumes-claims) ] .debug[(automatically generated title slide)] --- # Running a Consul cluster - Here is a good use-case for Stateful sets! - We are going to deploy a Consul cluster with 3 nodes - Consul is a highly-available key/value store (like etcd or Zookeeper) - One easy way to bootstrap a cluster is to tell each node: - the addresses of other nodes - how many nodes are expected (to know when quorum is reached) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Bootstrapping a Consul cluster *After reading the Consul documentation carefully (and/or asking around), we figure out the minimal command-line to run our Consul cluster.* ``` consul agent -data-dir=/consul/data -client=<IP_ADDRESS> -server -ui \ -bootstrap-expect=3 \ -retry-join=`X.X.X.X` \ -retry-join=`Y.Y.Y.Y` ``` - Replace X.X.X.X and Y.Y.Y.Y with the addresses of other nodes - A node can add its own address (it will work fine) - ... Which means that we can use the same command-line on all nodes (convenient!) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Cloud Auto-join - Since version 1.4.0, Consul can use the Kubernetes API to find its peers - This is called [Cloud Auto-join] - Instead of passing an IP address, we need to pass a parameter like this: ``` consul agent -retry-join "provider=k8s label_selector=\"app=consul\"" ``` - Consul needs to be able to talk to the Kubernetes API - We can provide a `kubeconfig` file - If Consul runs in a pod, it will use the *service account* of the pod [Cloud Auto-join]: https://www.consul.io/docs/agent/cloud-auto-join.html#kubernetes-k8s- .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Setting up Cloud auto-join - We need to create a service account for Consul - We need to create a role that can `list` and `get` pods - We need to bind that role to the service account - And of course, we need to make sure that Consul pods use that service account .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Putting it all together - The file `k8s/consul-1.yaml` defines the required resources (service account, role, role binding, service, stateful set) - Inspired by this [excellent tutorial](https://github.com/kelseyhightower/consul-on-kubernetes) by Kelsey Hightower (many features from the original tutorial were removed for simplicity) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Running our Consul cluster - We'll use the provided YAML file .exercise[ - Create the stateful set and associated service: ```bash kubectl apply -f ~/container.training/k8s/consul-1.yaml ``` - Check the logs as the pods come up one after another: ```bash stern consul ``` - Check the health of the cluster: ```bash kubectl exec consul-0 -- consul members ``` ] .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Caveats - The scheduler may place two Consul pods on the same node - if that node fails, we lose two Consul pods at the same time - this will cause the cluster to fail - Scaling down the cluster will cause it to fail - when a Consul member leaves the cluster, it needs to inform the others - otherwise, the last remaining node doesn't have quorum and stops functioning - This Consul cluster doesn't use real persistence yet - data is stored in the containers' ephemeral filesystem - if a pod fails, its replacement starts from a blank slate .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Improving pod placement - We need to tell the scheduler: *do not put two of these pods on the same node!* - This is done with an `affinity` section like the following one: ```yaml affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - consul topologyKey: kubernetes.io/hostname ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Using a lifecycle hook - When a Consul member leaves the cluster, it needs to execute: ```bash consul leave ``` - This is done with a `lifecycle` section like the following one: ```yaml lifecycle: preStop: exec: command: - /bin/sh - -c - consul leave ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Running a better Consul cluster - Let's try to add the scheduling constraint and lifecycle hook - We can do that in the same namespace or another one (as we like) - If we do that in the same namespace, we will see a rolling update (pods will be replaced one by one) .exercise[ - Deploy a better Consul cluster: ```bash kubectl apply -f ~/container.training/k8s/consul-2.yaml ``` ] .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Still no persistence, though - We aren't using actual persistence yet (no `volumeClaimTemplate`, Persistent Volume, etc.) - What happens if we lose a pod? - a new pod gets rescheduled (with an empty state) - the new pod tries to connect to the two others - it will be accepted (after 1-2 minutes of instability) - and it will retrieve the data from the other pods .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Failure modes - What happens if we lose two pods? - manual repair will be required - we will need to instruct the remaining one to act solo - then rejoin new pods - What happens if we lose three pods? (aka all of them) - we lose all the data (ouch) - If we run Consul without persistent storage, backups are a good idea! .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-persistent-volumes-claims class: title Persistent Volumes Claims .nav[ [Previous section](#toc-running-a-consul-cluster) | [Back to table of contents](#toc-module-8) | [Next section](#toc-local-persistent-volumes) ] .debug[(automatically generated title slide)] --- # Persistent Volumes Claims - Our Pods can use a special volume type: a *Persistent Volume Claim* - A Persistent Volume Claim (PVC) is also a Kubernetes resource (visible with `kubectl get persistentvolumeclaims` or `kubectl get pvc`) - A PVC is not a volume; it is a *request for a volume* - It should indicate at least: - the size of the volume (e.g. "5 GiB") - the access mode (e.g. "read-write by a single pod") .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## What's in a PVC? - A PVC contains at least: - a list of *access modes* (ReadWriteOnce, ReadOnlyMany, ReadWriteMany) - a size (interpreted as the minimal storage space needed) - It can also contain optional elements: - a selector (to restrict which actual volumes it can use) - a *storage class* (used by dynamic provisioning, more on that later) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## What does a PVC look like? Here is a manifest for a basic PVC: ```yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: my-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Using a Persistent Volume Claim Here is a Pod definition like the ones shown earlier, but using a PVC: ```yaml apiVersion: v1 kind: Pod metadata: name: pod-using-a-claim spec: containers: - image: ... name: container-using-a-claim volumeMounts: - mountPath: /my-vol name: my-volume volumes: - name: my-volume persistentVolumeClaim: claimName: my-claim ``` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Creating and using Persistent Volume Claims - PVCs can be created manually and used explicitly (as shown on the previous slides) - They can also be created and used through Stateful Sets (this will be shown later) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Lifecycle of Persistent Volume Claims - When a PVC is created, it starts existing in "Unbound" state (without an associated volume) - A Pod referencing an unbound PVC will not start (the scheduler will wait until the PVC is bound to place it) - A special controller continuously monitors PVCs to associate them with PVs - If no PV is available, one must be created: - manually (by operator intervention) - using a *dynamic provisioner* (more on that later) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- class: extra-details ## Which PV gets associated to a PVC? - The PV must satisfy the PVC constraints (access mode, size, optional selector, optional storage class) - The PVs with the closest access mode are picked - Then the PVs with the closest size - It is possible to specify a `claimRef` when creating a PV (this will associate it to the specified PVC, but only if the PV satisfies all the requirements of the PVC; otherwise another PV might end up being picked) - For all the details about the PersistentVolumeClaimBinder, check [this doc](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/persistent-storage.md#matching-and-binding) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Persistent Volume Claims and Stateful sets - A Stateful set can define one (or more) `volumeClaimTemplate` - Each `volumeClaimTemplate` will create one Persistent Volume Claim per pod - Each pod will therefore have its own individual volume - These volumes are numbered (like the pods) - Example: - a Stateful set is named `db` - it is scaled to replicas - it has a `volumeClaimTemplate` named `data` - then it will create pods `db-0`, `db-1`, `db-2` - these pods will have volumes named `data-db-0`, `data-db-1`, `data-db-2` .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Persistent Volume Claims are sticky - When updating the stateful set (e.g. image upgrade), each pod keeps its volume - When pods get rescheduled (e.g. node failure), they keep their volume (this requires a storage system that is not node-local) - These volumes are not automatically deleted (when the stateful set is scaled down or deleted) - If a stateful set is scaled back up later, the pods get their data back .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Dynamic provisioners - A *dynamic provisioner* monitors unbound PVCs - It can create volumes (and the corresponding PV) on the fly - This requires the PVCs to have a *storage class* (annotation `volume.beta.kubernetes.io/storage-provisioner`) - A dynamic provisioner only acts on PVCs with the right storage class (it ignores the other ones) - Just like `LoadBalancer` services, dynamic provisioners are optional (i.e. our cluster may or may not have one pre-installed) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## What's a Storage Class? - A Storage Class is yet another Kubernetes API resource (visible with e.g. `kubectl get storageclass` or `kubectl get sc`) - It indicates which *provisioner* to use (which controller will create the actual volume) - And arbitrary parameters for that provisioner (replication levels, type of disk ... anything relevant!) - Storage Classes are required if we want to use [dynamic provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/) (but we can also create volumes manually, and ignore Storage Classes) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## The default storage class - At most one storage class can be marked as the default class (by annotating it with `storageclass.kubernetes.io/is-default-class=true`) - When a PVC is created, it will be annotated with the default storage class (unless it specifies an explicit storage class) - This only happens at PVC creation (existing PVCs are not updated when we mark a class as the default one) .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Dynamic provisioning setup This is how we can achieve fully automated provisioning of persistent storage. 1. Configure a storage system. (It needs to have an API, or be capable of automated provisioning of volumes.) 2. Install a dynamic provisioner for this storage system. (This is some specific controller code.) 3. Create a Storage Class for this system. (It has to match what the dynamic provisioner is expecting.) 4. Annotate the Storage Class to be the default one. .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- ## Dynamic provisioning usage After setting up the system (previous slide), all we need to do is: *Create a Stateful Set that makes use of a `volumeClaimTemplate`.* This will trigger the following actions. 1. The Stateful Set creates PVCs according to the `volumeClaimTemplate`. 2. The Stateful Set creates Pods using these PVCs. 3. The PVCs are automatically annotated with our Storage Class. 4. The dynamic provisioner provisions volumes and creates the corresponding PVs. 5. The PersistentVolumeClaimBinder associates the PVs and the PVCs together. 6. PVCs are now bound, the Pods can start. ??? :EN:- Deploying apps with Stateful Sets :EN:- Example: deploying a Consul cluster :EN:- Understanding Persistent Volume Claims and Storage Classes :FR:- Déployer une application avec un *Stateful Set* :FR:- Example : lancer un cluster Consul :FR:- Comprendre les *Persistent Volume Claims* et *Storage Classes* .debug[[k8s/statefulsets.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/statefulsets.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-local-persistent-volumes class: title Local Persistent Volumes .nav[ [Previous section](#toc-persistent-volumes-claims) | [Back to table of contents](#toc-module-8) | [Next section](#toc-highly-available-persistent-volumes) ] .debug[(automatically generated title slide)] --- # Local Persistent Volumes - We want to run that Consul cluster *and* actually persist data - But we don't have a distributed storage system - We are going to use local volumes instead (similar conceptually to `hostPath` volumes) - We can use local volumes without installing extra plugins - However, they are tied to a node - If that node goes down, the volume becomes unavailable .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## With or without dynamic provisioning - We will deploy a Consul cluster *with* persistence - That cluster's StatefulSet will create PVCs - These PVCs will remain unbound¹, until we will create local volumes manually (we will basically do the job of the dynamic provisioner) - Then, we will see how to automate that with a dynamic provisioner .footnote[¹Unbound = without an associated Persistent Volume.] .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## If we have a dynamic provisioner ... - The labs in this section assume that we *do not* have a dynamic provisioner - If we do have one, we need to disable it .exercise[ - Check if we have a dynamic provisioner: ```bash kubectl get storageclass ``` - If the output contains a line with `(default)`, run this command: ```bash kubectl annotate sc storageclass.kubernetes.io/is-default-class- --all ``` - Check again that it is no longer marked as `(default)` ] .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Deploying Consul - Let's use a new manifest for our Consul cluster - The only differences between that file and the previous one are: - `volumeClaimTemplate` defined in the Stateful Set spec - the corresponding `volumeMounts` in the Pod spec .exercise[ - Apply the persistent Consul YAML file: ```bash kubectl apply -f ~/container.training/k8s/consul-3.yaml ``` ] .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Observing the situation - Let's look at Persistent Volume Claims and Pods .exercise[ - Check that we now have an unbound Persistent Volume Claim: ```bash kubectl get pvc ``` - We don't have any Persistent Volume: ```bash kubectl get pv ``` - The Pod `consul-0` is not scheduled yet: ```bash kubectl get pods -o wide ``` ] *Hint: leave these commands running with `-w` in different windows.* .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Explanations - In a Stateful Set, the Pods are started one by one - `consul-1` won't be created until `consul-0` is running - `consul-0` has a dependency on an unbound Persistent Volume Claim - The scheduler won't schedule the Pod until the PVC is bound (because the PVC might be bound to a volume that is only available on a subset of nodes; for instance EBS are tied to an availability zone) .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Creating Persistent Volumes - Let's create 3 local directories (`/mnt/consul`) on node2, node3, node4 - Then create 3 Persistent Volumes corresponding to these directories .exercise[ - Create the local directories: ```bash for NODE in node2 node3 node4; do ssh $NODE sudo mkdir -p /mnt/consul done ``` - Create the PV objects: ```bash kubectl apply -f ~/container.training/k8s/volumes-for-consul.yaml ``` ] .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Check our Consul cluster - The PVs that we created will be automatically matched with the PVCs - Once a PVC is bound, its pod can start normally - Once the pod `consul-0` has started, `consul-1` can be created, etc. - Eventually, our Consul cluster is up, and backend by "persistent" volumes .exercise[ - Check that our Consul clusters has 3 members indeed: ```bash kubectl exec consul-0 -- consul members ``` ] .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Devil is in the details (1/2) - The size of the Persistent Volumes is bogus (it is used when matching PVs and PVCs together, but there is no actual quota or limit) .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Devil is in the details (2/2) - This specific example worked because we had exactly 1 free PV per node: - if we had created multiple PVs per node ... - we could have ended with two PVCs bound to PVs on the same node ... - which would have required two pods to be on the same node ... - which is forbidden by the anti-affinity constraints in the StatefulSet - To avoid that, we need to associated the PVs with a Storage Class that has: ```yaml volumeBindingMode: WaitForFirstConsumer ``` (this means that a PVC will be bound to a PV only after being used by a Pod) - See [this blog post](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/) for more details .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Bulk provisioning - It's not practical to manually create directories and PVs for each app - We *could* pre-provision a number of PVs across our fleet - We could even automate that with a Daemon Set: - creating a number of directories on each node - creating the corresponding PV objects - We also need to recycle volumes - ... This can quickly get out of hand .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Dynamic provisioning - We could also write our own provisioner, which would: - watch the PVCs across all namespaces - when a PVC is created, create a corresponding PV on a node - Or we could use one of the dynamic provisioners for local persistent volumes (for instance the [Rancher local path provisioner](https://github.com/rancher/local-path-provisioner)) .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- ## Strategies for local persistent volumes - Remember, when a node goes down, the volumes on that node become unavailable - High availability will require another layer of replication (like what we've just seen with Consul; or primary/secondary; etc) - Pre-provisioning PVs makes sense for machines with local storage (e.g. cloud instance storage; or storage directly attached to a physical machine) - Dynamic provisioning makes sense for large number of applications (when we can't or won't dedicate a whole disk to a volume) - It's possible to mix both (using distinct Storage Classes) ??? :EN:- Static vs dynamic volume provisioning :EN:- Example: local persistent volume provisioner :FR:- Création statique ou dynamique de volumes :FR:- Exemple : création de volumes locaux .debug[[k8s/local-persistent-volumes.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/local-persistent-volumes.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg)] --- name: toc-highly-available-persistent-volumes class: title Highly available Persistent Volumes .nav[ [Previous section](#toc-local-persistent-volumes) | [Back to table of contents](#toc-module-8) | [Next section](#toc-next-steps) ] .debug[(automatically generated title slide)] --- # Highly available Persistent Volumes - How can we achieve true durability? - How can we store data that would survive the loss of a node? -- - We need to use Persistent Volumes backed by highly available storage systems - There are many ways to achieve that: - leveraging our cloud's storage APIs - using NAS/SAN systems or file servers - distributed storage systems -- - We are going to see one distributed storage system in action .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Our test scenario - We will set up a distributed storage system on our cluster - We will use it to deploy a SQL database (PostgreSQL) - We will insert some test data in the database - We will disrupt the node running the database - We will see how it recovers .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Portworx - Portworx is a *commercial* persistent storage solution for containers - It works with Kubernetes, but also Mesos, Swarm ... - It provides [hyper-converged](https://en.wikipedia.org/wiki/Hyper-converged_infrastructure) storage (=storage is provided by regular compute nodes) - We're going to use it here because it can be deployed on any Kubernetes cluster (it doesn't require any particular infrastructure) - We don't endorse or support Portworx in any particular way (but we appreciate that it's super easy to install!) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## A useful reminder - We're installing Portworx because we need a storage system - If you are using AKS, EKS, GKE ... you already have a storage system (but you might want another one, e.g. to leverage local storage) - If you have setup Kubernetes yourself, there are other solutions available too - on premises, you can use a good old SAN/NAS - on a private cloud like OpenStack, you can use e.g. Cinder - everywhere, you can use other systems, e.g. Gluster, StorageOS .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Installing Portworx - Portworx installation is relatively simple - ... But we made it *even simpler!* - We are going to use a YAML manifest that will take care of everything - Warning: this manifest is customized for a very specific setup (like the VMs that we provide during workshops and training sessions) - It will probably *not work* If you are using a different setup (like Docker Desktop, k3s, MicroK8S, Minikube ...) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## The simplified Portworx installer - The Portworx installation will take a few minutes - Let's start it, then we'll explain what happens behind the scenes .exercise[ - Install Portworx: ```bash kubectl apply -f ~/container.training/k8s/portworx.yaml ``` ] *Note: this was tested with Kubernetes 1.18. Newer versions may or may not work.* .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## What's in this YAML manifest? - Portworx installation itself, pre-configured for our setup - A default *Storage Class* using Portworx - A *Daemon Set* to create loop devices on each node of the cluster .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Portworx installation - The official way to install Portworx is to use [PX-Central](https://central.portworx.com/) (this requires a free account) - PX-Central will ask us a few questions about our cluster (Kubernetes version, on-prem/cloud deployment, etc.) - Using our answers, it will generate a YAML manifest that we can use .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Portworx storage configuration - Portworx needs at least one *block device* - Block device = disk or partition on a disk - We can see block devices with `lsblk` (or `cat /proc/partitions` if we're old school like that!) - If we don't have a spare disk or partition, we can use a *loop device* - A loop device is a block device actually backed by a file - These are frequently used to mount ISO (CD/DVD) images or VM disk images .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Setting up a loop device - Our `portworx.yaml` manifest includes a *Daemon Set* that will: - create a 10 GB (empty) file on each node - load the `loop` module (if it's not already loaded) - associate a loop device with the 10 GB file - After these steps, we have a block device that Portworx can use .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Implementation details - The file is `/portworx.blk` (it is a [sparse file](https://en.wikipedia.org/wiki/Sparse_file) created with `truncate`) - The loop device is `/dev/loop4` - This can be verified by running `sudo losetup` - The *Daemon Set* uses a privileged *Init Container* - We can check the logs of that container with: ```bash kubectl logs --selector=app=setup-loop4-for-portworx \ -c setup-loop4-for-portworx ``` .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Waiting for Portworx to be ready - The installation process will take a few minutes .exercise[ - Check out the logs: ```bash stern -n kube-system portworx ``` - Wait until it gets quiet (you should see `portworx service is healthy`, too) ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Dynamic provisioning of persistent volumes - We are going to run PostgreSQL in a Stateful set - The Stateful set will specify a `volumeClaimTemplate` - That `volumeClaimTemplate` will create Persistent Volume Claims - Kubernetes' [dynamic provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/) will satisfy these Persistent Volume Claims (by creating Persistent Volumes and binding them to the claims) - The Persistent Volumes are then available for the PostgreSQL pods .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Storage Classes - It's possible that multiple storage systems are available - Or, that a storage system offers multiple tiers of storage (SSD vs. magnetic; mirrored or not; etc.) - We need to tell Kubernetes *which* system and tier to use - This is achieved by creating a Storage Class - A `volumeClaimTemplate` can indicate which Storage Class to use - It is also possible to mark a Storage Class as "default" (it will be used if a `volumeClaimTemplate` doesn't specify one) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Check our default Storage Class - The YAML manifest applied earlier should define a default storage class .exercise[ - Check that we have a default storage class: ```bash kubectl get storageclass ``` ] There should be a storage class showing as `portworx-replicated (default)`. .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Our default Storage Class This is our Storage Class (in `k8s/storage-class.yaml`): ```yaml kind: StorageClass apiVersion: storage.k8s.io/v1beta1 metadata: name: portworx-replicated annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: kubernetes.io/portworx-volume parameters: repl: "2" priority_io: "high" ``` - It says "use Portworx to create volumes and keep 2 replicas of these volumes" - The annotation makes this Storage Class the default one .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Our Postgres Stateful set - The next slide shows `k8s/postgres.yaml` - It defines a Stateful set - With a `volumeClaimTemplate` requesting a 1 GB volume - That volume will be mounted to `/var/lib/postgresql/data` - There is another little detail: we enable the `stork` scheduler - The `stork` scheduler is optional (it's specific to Portworx) - It helps the Kubernetes scheduler to colocate the pod with its volume (see [this blog post](https://portworx.com/stork-storage-orchestration-kubernetes/) for more details about that) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- .small[ ```yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: selector: matchLabels: app: postgres serviceName: postgres template: metadata: labels: app: postgres spec: schedulerName: stork containers: - name: postgres image: postgres:12 env: - name: POSTGRES_HOST_AUTH_METHOD value: trust volumeMounts: - mountPath: /var/lib/postgresql/data name: postgres volumeClaimTemplates: - metadata: name: postgres spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1Gi ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Creating the Stateful set - Before applying the YAML, watch what's going on with `kubectl get events -w` .exercise[ - Apply that YAML: ```bash kubectl apply -f ~/container.training/k8s/postgres.yaml ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Testing our PostgreSQL pod - We will use `kubectl exec` to get a shell in the pod - Good to know: we need to use the `postgres` user in the pod .exercise[ - Get a shell in the pod, as the `postgres` user: ```bash kubectl exec -ti postgres-0 -- su postgres ``` - Check that default databases have been created correctly: ```bash psql -l ``` ] (This should show us 3 lines: postgres, template0, and template1.) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Inserting data in PostgreSQL - We will create a database and populate it with `pgbench` .exercise[ - Create a database named `demo`: ```bash createdb demo ``` - Populate it with `pgbench`: ```bash pgbench -i demo ``` ] - The `-i` flag means "create tables" - If you want more data in the test tables, add e.g. `-s 10` (to get 10x more rows) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Checking how much data we have now - The `pgbench` tool inserts rows in table `pgbench_accounts` .exercise[ - Check that the `demo` base exists: ```bash psql -l ``` - Check how many rows we have in `pgbench_accounts`: ```bash psql demo -c "select count(*) from pgbench_accounts" ``` - Check that `pgbench_history` is currently empty: ```bash psql demo -c "select count(*) from pgbench_history" ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Testing the load generator - Let's use `pgbench` to generate a few transactions .exercise[ - Run `pgbench` for 10 seconds, reporting progress every second: ```bash pgbench -P 1 -T 10 demo ``` - Check the size of the history table now: ```bash psql demo -c "select count(*) from pgbench_history" ``` ] Note: on small cloud instances, a typical speed is about 100 transactions/second. .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Generating transactions - Now let's use `pgbench` to generate more transactions - While it's running, we will disrupt the database server .exercise[ - Run `pgbench` for 10 minutes, reporting progress every second: ```bash pgbench -P 1 -T 600 demo ``` - You can use a longer time period if you need more time to run the next steps ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Find out which node is hosting the database - We can find that information with `kubectl get pods -o wide` .exercise[ - Check the node running the database: ```bash kubectl get pod postgres-0 -o wide ``` ] We are going to disrupt that node. -- By "disrupt" we mean: "disconnect it from the network". .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Disconnect the node - We will use `iptables` to block all traffic exiting the node (except SSH traffic, so we can repair the node later if needed) .exercise[ - SSH to the node to disrupt: ```bash ssh `nodeX` ``` - Allow SSH traffic leaving the node, but block all other traffic: ```bash sudo iptables -I OUTPUT -p tcp --sport 22 -j ACCEPT sudo iptables -I OUTPUT 2 -j DROP ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Check that the node is disconnected .exercise[ - Check that the node can't communicate with other nodes: ``` ping node1 ``` - Logout to go back on `node1` - Watch the events unfolding with `kubectl get events -w` and `kubectl get pods -w` ] - It will take some time for Kubernetes to mark the node as unhealthy - Then it will attempt to reschedule the pod to another node - In about a minute, our pod should be up and running again .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Check that our data is still available - We are going to reconnect to the (new) pod and check .exercise[ - Get a shell on the pod: ```bash kubectl exec -ti postgres-0 -- su postgres ``` - Check how many transactions are now in the `pgbench_history` table: ```bash psql demo -c "select count(*) from pgbench_history" ``` ] If the 10-second test that we ran earlier gave e.g. 80 transactions per second, and we failed the node after 30 seconds, we should have about 2400 row in that table. .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Double-check that the pod has really moved - Just to make sure the system is not bluffing! .exercise[ - Look at which node the pod is now running on ```bash kubectl get pod postgres-0 -o wide ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Re-enable the node - Let's fix the node that we disconnected from the network .exercise[ - SSH to the node: ```bash ssh `nodeX` ``` - Remove the iptables rule blocking traffic: ```bash sudo iptables -D OUTPUT 2 ``` ] .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## A few words about this PostgreSQL setup - In a real deployment, you would want to set a password - This can be done by creating a `secret`: ``` kubectl create secret generic postgres \ --from-literal=password=$(base64 /dev/urandom | head -c16) ``` - And then passing that secret to the container: ```yaml env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: postgres key: password ``` .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Troubleshooting Portworx - If we need to see what's going on with Portworx: ``` PXPOD=$(kubectl -n kube-system get pod -l name=portworx -o json | jq -r .items[0].metadata.name) kubectl -n kube-system exec $PXPOD -- /opt/pwx/bin/pxctl status ``` - We can also connect to Lighthouse (a web UI) - check the port with `kubectl -n kube-system get svc px-lighthouse` - connect to that port - the default login/password is `admin/Password1` - then specify `portworx-service` as the endpoint .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Removing Portworx - Portworx provides a storage driver - It needs to place itself "above" the Kubelet (it installs itself straight on the nodes) - To remove it, we need to do more than just deleting its Kubernetes resources - It is done by applying a special label: ``` kubectl label nodes --all px/enabled=remove --overwrite ``` - Then removing a bunch of local files: ``` sudo chattr -i /etc/pwx/.private.json sudo rm -rf /etc/pwx /opt/pwx ``` (on each node where Portworx was running) .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: extra-details ## Dynamic provisioning without a provider - What if we want to use Stateful sets without a storage provider? - We will have to create volumes manually (by creating Persistent Volume objects) - These volumes will be automatically bound with matching Persistent Volume Claims - We can use local volumes (essentially bind mounts of host directories) - Of course, these volumes won't be available in case of node failure - Check [this blog post](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/) for more information and gotchas .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- ## Acknowledgements The Portworx installation tutorial, and the PostgreSQL example, were inspired by [Portworx examples on Katacoda](https://katacoda.com/portworx/scenarios/), in particular: - [installing Portworx on Kubernetes](https://www.katacoda.com/portworx/scenarios/deploy-px-k8s) (with adapatations to use a loop device and an embedded key/value store) - [persistent volumes on Kubernetes using Portworx](https://www.katacoda.com/portworx/scenarios/px-k8s-vol-basic) (with adapatations to specify a default Storage Class) - [HA PostgreSQL on Kubernetes with Portworx](https://www.katacoda.com/portworx/scenarios/px-k8s-postgres-all-in-one) (with adaptations to use a Stateful Set and simplify PostgreSQL's setup) ??? :EN:- Using highly available persistent volumes :EN:- Example: deploying a database that can withstand node outages :FR:- Utilisation de volumes à haute disponibilité :FR:- Exemple : déployer une base de données survivant à la défaillance d'un nœud .debug[[k8s/portworx.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/portworx.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg)] --- name: toc-next-steps class: title Next steps .nav[ [Previous section](#toc-highly-available-persistent-volumes) | [Back to table of contents](#toc-module-9) | [Next section](#toc-last-words) ] .debug[(automatically generated title slide)] --- # Next steps *Alright, how do I get started and containerize my apps?* -- Suggested containerization checklist: .checklist[ - write a Dockerfile for one service in one app - write Dockerfiles for the other (buildable) services - write a Compose file for that whole app - make sure that devs are empowered to run the app in containers - set up automated builds of container images from the code repo - set up a CI pipeline using these container images - set up a CD pipeline (for staging/QA) using these images ] And *then* it is time to look at orchestration! .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Options for our first production cluster - Get a managed cluster from a major cloud provider (AKS, EKS, GKE...) (price: $, difficulty: medium) - Hire someone to deploy it for us (price: $$, difficulty: easy) - Do it ourselves (price: $-$$$, difficulty: hard) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## One big cluster vs. multiple small ones - Yes, it is possible to have prod+dev in a single cluster (and implement good isolation and security with RBAC, network policies...) - But it is not a good idea to do that for our first deployment - Start with a production cluster + at least a test cluster - Implement and check RBAC and isolation on the test cluster (e.g. deploy multiple test versions side-by-side) - Make sure that all our devs have usable dev clusters (whether it's a local minikube or a full-blown multi-node cluster) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Namespaces - Namespaces let you run multiple identical stacks side by side - Two namespaces (e.g. `blue` and `green`) can each have their own `redis` service - Each of the two `redis` services has its own `ClusterIP` - CoreDNS creates two entries, mapping to these two `ClusterIP` addresses: `redis.blue.svc.cluster.local` and `redis.green.svc.cluster.local` - Pods in the `blue` namespace get a *search suffix* of `blue.svc.cluster.local` - As a result, resolving `redis` from a pod in the `blue` namespace yields the "local" `redis` .warning[This does not provide *isolation*! That would be the job of network policies.] .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Relevant sections - [Namespaces](kube-selfpaced.yml.html#toc-namespaces) - [Network Policies](kube-selfpaced.yml.html#toc-network-policies) - [Role-Based Access Control](kube-selfpaced.yml.html#toc-authentication-and-authorization) (covers permissions model, user and service accounts management ...) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Stateful services (databases etc.) - As a first step, it is wiser to keep stateful services *outside* of the cluster - Exposing them to pods can be done with multiple solutions: - `ExternalName` services
(`redis.blue.svc.cluster.local` will be a `CNAME` record) - `ClusterIP` services with explicit `Endpoints`
(instead of letting Kubernetes generate the endpoints from a selector) - Ambassador services
(application-level proxies that can provide credentials injection and more) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Stateful services (second take) - If we want to host stateful services on Kubernetes, we can use: - a storage provider - persistent volumes, persistent volume claims - stateful sets - Good questions to ask: - what's the *operational cost* of running this service ourselves? - what do we gain by deploying this stateful service on Kubernetes? - Relevant sections: [Volumes](kube-selfpaced.yml.html#toc-volumes) | [Stateful Sets](kube-selfpaced.yml.html#toc-stateful-sets) | [Persistent Volumes](kube-selfpaced.yml.html#toc-highly-available-persistent-volumes) - Excellent [blog post](http://www.databasesoup.com/2018/07/should-i-run-postgres-on-kubernetes.html) tackling the question: “Should I run Postgres on Kubernetes?” .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## HTTP traffic handling - *Services* are layer 4 constructs - HTTP is a layer 7 protocol - It is handled by *ingresses* (a different resource kind) - *Ingresses* allow: - virtual host routing - session stickiness - URI mapping - and much more! - [This section](kube-selfpaced.yml.html#toc-exposing-http-services-with-ingress-resources) shows how to expose multiple HTTP apps using [Træfik](https://docs.traefik.io/user-guide/kubernetes/) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Logging - Logging is delegated to the container engine - Logs are exposed through the API - Logs are also accessible through local files (`/var/log/containers`) - Log shipping to a central platform is usually done through these files (e.g. with an agent bind-mounting the log directory) - [This section](kube-selfpaced.yml.html#toc-centralized-logging) shows how to do that with [Fluentd](https://docs.fluentd.org/v0.12/articles/kubernetes-fluentd) and the EFK stack .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Metrics - The kubelet embeds [cAdvisor](https://github.com/google/cadvisor), which exposes container metrics (cAdvisor might be separated in the future for more flexibility) - It is a good idea to start with [Prometheus](https://prometheus.io/) (even if you end up using something else) - Starting from Kubernetes 1.8, we can use the [Metrics API](https://kubernetes.io/docs/tasks/debug-application-cluster/core-metrics-pipeline/) - [Heapster](https://github.com/kubernetes/heapster) was a popular add-on (but is being [deprecated](https://github.com/kubernetes/heapster/blob/master/docs/deprecation.md) starting with Kubernetes 1.11) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Managing the configuration of our applications - Two constructs are particularly useful: secrets and config maps - They allow to expose arbitrary information to our containers - **Avoid** storing configuration in container images (There are some exceptions to that rule, but it's generally a Bad Idea) - **Never** store sensitive information in container images (It's the container equivalent of the password on a post-it note on your screen) - [This section](kube-selfpaced.yml.html#toc-managing-configuration) shows how to manage app config with config maps (among others) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Managing stack deployments - Applications are made of many resources (Deployments, Services, and much more) - We need to automate the creation / update / management of these resources - There is no "absolute best" tool or method; it depends on: - the size and complexity of our stack(s) - how often we change it (i.e. add/remove components) - the size and skills of our team .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## A few tools to manage stacks - Shell scripts invoking `kubectl` - YAML resource manifests committed to a repo - [Kustomize](https://github.com/kubernetes-sigs/kustomize) (YAML manifests + patches applied on top) - [Helm](https://github.com/kubernetes/helm) (YAML manifests + templating engine) - [Spinnaker](https://www.spinnaker.io/) (Netflix' CD platform) - [Brigade](https://brigade.sh/) (event-driven scripting; no YAML) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Cluster federation -- ![Star Trek Federation](images/startrek-federation.jpg) -- Sorry Star Trek fans, this is not the federation you're looking for! -- (If I add "Your cluster is in another federation" I might get a 3rd fandom wincing!) .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- ## Cluster federation - Kubernetes master operation relies on etcd - etcd uses the [Raft](https://raft.github.io/) protocol - Raft recommends low latency between nodes - What if our cluster spreads to multiple regions? -- - Break it down in local clusters - Regroup them in a *cluster federation* - Synchronize resources across clusters - Discover resources across clusters .debug[[k8s/whatsnext.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/whatsnext.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg)] --- name: toc-last-words class: title Last words .nav[ [Previous section](#toc-next-steps) | [Back to table of contents](#toc-module-9) | [Next section](#toc-links-and-resources) ] .debug[(automatically generated title slide)] --- # Last words - Congratulations! - We learned a lot about Kubernetes, its internals, its advanced concepts -- - That was just the easy part - The hard challenges will revolve around *culture* and *people* -- - ... What does that mean? .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Running an app involves many steps - Write the app - Tests, QA ... - Ship *something* (more on that later) - Provision resources (e.g. VMs, clusters) - Deploy the *something* on the resources - Manage, maintain, monitor the resources - Manage, maintain, monitor the app - And much more .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Who does what? - The old "devs vs ops" division has changed - In some organizations, "ops" are now called "SRE" or "platform" teams (and they have very different sets of skills) - Do you know which team is responsible for each item on the list on the previous page? - Acknowledge that a lot of tasks are outsourced (e.g. if we add "buy/rack/provision machines" in that list) .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## What do we ship? - Some organizations embrace "you build it, you run it" - When "build" and "run" are owned by different teams, where's the line? - What does the "build" team ship to the "run" team? - Let's see a few options, and what they imply .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Shipping code - Team "build" ships code (hopefully in a repository, identified by a commit hash) - Team "run" containerizes that code ✔ no extra work for developers ❌ very little advantage of using containers .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Shipping container images - Team "build" ships container images (hopefully built automatically from a source repository) - Team "run" uses theses images to create e.g. Kubernetes resources ✔ universal artefact (support all languages uniformly) ✔ easy to start a single component (good for monoliths) ❌ complex applications will require a lot of extra work ❌ adding/removing components in the stack also requires extra work ❌ complex applications will run very differently between dev and prod .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Shipping Compose files (Or another kind of dev-centric manifest) - Team "build" ships a manifest that works on a single node (as well as images, or ways to build them) - Team "run" adapts that manifest to work on a cluster ✔ all teams can start the stack in a reliable, deterministic manner ❌ adding/removing components still requires *some* work (but less than before) ❌ there will be *some* differences between dev and prod .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Shipping Kubernetes manifests - Team "build" ships ready-to-run manifests (YAML, Helm charts, Kustomize ...) - Team "run" adjusts some parameters and monitors the application ✔ parity between dev and prod environments ✔ "run" team can focus on SLAs, SLOs, and overall quality ❌ requires *a lot* of extra work (and new skills) from the "build" team ❌ Kubernetes is not a very convenient development platform (at least, not yet) .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## What's the right answer? - It depends on our teams - existing skills (do they know how to do it?) - availability (do they have the time to do it?) - potential skills (can they learn to do it?) - It depends on our culture - owning "run" often implies being on call - do we reward on-call duty without encouraging hero syndrome? - do we give people resources (time, money) to learn? .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- class: extra-details ## Tools to develop on Kubernetes *If we decide to make Kubernetes the primary development platform, here are a few tools that can help us.* - Docker Desktop - Draft - Minikube - Skaffold - Tilt - ... .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Where do we run? - Managed vs. self-hosted - Cloud vs. on-premises - If cloud: public vs. private - Which vendor/distribution to pick? - Which versions/features to enable? .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Developer experience *These questions constitute a quick "smoke test" for our strategy:* - How do we on-board a new developer? - What do they need to install to get a dev stack? - How does a code change make it from dev to prod? - How does someone add a component to a stack? .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- ## Some guidelines - Start small - Outsource what we don't know - Start simple, and stay simple as long as possible (try to stay away from complex features that we don't need) - Automate (regularly check that we can successfully redeploy by following scripts) - Transfer knowledge (make sure everyone is on the same page/level) - Iterate! .debug[[k8s/lastwords.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/lastwords.md)] --- class: pic .interstitial[![Image separating from the next module](https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg)] --- name: toc-links-and-resources class: title Links and resources .nav[ [Previous section](#toc-last-words) | [Back to table of contents](#toc-module-9) | [Next section](#toc-) ] .debug[(automatically generated title slide)] --- # Links and resources All things Kubernetes: - [Kubernetes Community](https://kubernetes.io/community/) - Slack, Google Groups, meetups - [Kubernetes on StackOverflow](https://stackoverflow.com/questions/tagged/kubernetes) - [Play With Kubernetes Hands-On Labs](https://medium.com/@marcosnils/introducing-pwk-play-with-k8s-159fcfeb787b) All things Docker: - [Docker documentation](http://docs.docker.com/) - [Docker Hub](https://hub.docker.com) - [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker) - [Play With Docker Hands-On Labs](http://training.play-with-docker.com/) Everything else: - [Local meetups](https://www.meetup.com/) .footnote[These slides (and future updates) are on → http://container.training/] .debug[[k8s/links.md](https://github.com/jpetazzo/container.training/tree/master/slides/k8s/links.md)] --- class: title, self-paced Thank you! .debug[[shared/thankyou.md](https://github.com/jpetazzo/container.training/tree/master/slides/shared/thankyou.md)] --- class: title, in-person That's all, folks!
Questions? ![end](images/end.jpg) .debug[[shared/thankyou.md](https://github.com/jpetazzo/container.training/tree/master/slides/shared/thankyou.md)]


System Engineer
MELON PARK SOLUTION LIMITED
• Identify or address interoperability requirements.
• Collaborate with development teams to discuss, analyze, or resolve usability issues
• Document application and web site changes or change procedures.
• Test new software packages for use in web operations or other applications.


Program Description
This web application takes the user's date of birth and gender; then calculates the day of the week, they were born in.
The application then uses this input to assign the user their Akan Name.
The Akan naming convention is a Ghanaian cultural event that takes place among the Akan people of Ghana.
The name given possesses a deeper cultural meaning and is said to influence the person's character and soulfulness.


Welcome to the Train For Life Assessment. We are glad that you are taking the first step to improving your wellbeing. Click the link below to start taking your assessment!


All-articles component Axshare link: https://nm6pq4.axshare.com/#g=1&p=news_landing_page Dimensions:640x430px


names(lookup_table)
#> [1] "stateID" "billStatusDict" "billProgressDict"
#> [4] "billTypeDict" "billTextTypeDict" "billSponsorDict"
#> [7] "mimeTypeDict"
| status_id | status_desc |
|---|---|
| 0 | NA |
| 1 | Introduced |
| 2 | Engrossed |
| 3 | Enrolled |
| 4 | Passed |
| 5 | Vetoed |
| 6 | Failed |
| event_id | event_desc |
|---|---|
| 1 | Introduced |
| 2 | Engrossed |
| 3 | Enrolled |
| 4 | Passed |
| 5 | Vetoed |
| 6 | Failed |
| 7 | Override |
| 8 | Chaptered |
| 9 | Refer |
| 10 | Report Pass |
| 11 | Report DNP |
| 12 | Draft |
| bill_type_id | bill_type | bill_type_name |
|---|---|---|
| 1 | B | Bill |
| 2 | R | Resolution |
| 3 | CR | Concurrent Resolution |
| 4 | JR | Joint Resolution |
| 5 | JRCA | Joint Resolution Constitutional Amendment |
| 6 | EO | Executive Order |
| 7 | CA | Constitutional Amendment |
| 8 | M | Memorial |
| 9 | CL | Claim |
| 10 | C | Commendation |
| 11 | CSR | Committee Study Request |
| 12 | JM | Joint Memorial |
| 13 | P | Proclamation |
| 14 | SR | Study Request |
| 15 | A | Address |
| 16 | CM | Concurrent Memorial |
| 17 | I | Initiative |
| 18 | PET | Petition |
| 19 | SB | Study Bill |
| 20 | IP | Initiative Petition |
| 21 | RB | Repeal Bill |
| 22 | RM | Remonstration |
| 23 | CB | Committee Bill |
| bill_text_type_id | bill_text_type_desc |
|---|---|
| 1 | Introduced |
| 2 | Committee Substitute |
| 3 | Amended |
| 4 | Engrossed |
| 5 | Enrolled |
| 6 | Chaptered |
| 7 | Fiscal Note |
| 8 | Analysis |
| 9 | Draft |
| 10 | Conference Substitute |
| 11 | Prefiled |
| 12 | Veto Message |
| 13 | Veto Response |
| 14 | Substitute |


Westin Lohne
Product Designer
I'm a 20 year old student attending Carnegie Mellon University studying Human-Computer Interaction.
Last summer, I interned at Etsy as a UX Researcher. This summer, I'll intern at Facebook as a Product Designer.


Periodic Assessment Test Report for: {{state}}
Periodic Assessment Test Report for: {{state}} / {{districtHierarchy.districtName}}
Periodic Assessment Test Report for: {{state}}/ {{blockHierarchy.districtName}}/ {{blockHierarchy.blockName}}
Periodic Assessment Test Report for: {{state}}/ {{clusterHierarchy.districtName}}/ {{clusterHierarchy.blockName}}/ {{clusterHierarchy.clusterName}}
About the dashboard: This dashboard provides information about student's performance in the Periodic Assessment Test across all subjects and grades. The data has been collated at various administrative levels (i.e. District, Block, Cluster and School) and this dashboard allows you to view and download the data at these various administrative levels.


Jupyter Tricks
by Tara Furstenau
Jupyter Notebook, previously known as iPython Notebook, is a web based application for interactive computing. Kernels have been developed for many popular programming languages including Julia, Python and R which have been cleverly combined to form the new name Jupyter. These notebooks are a great way to record any data analysis work flow and are therefore great for reproducibility. Notebooks also allow you to keep all of your code in one place instead of having a directory full of random scripts and output. The integration of Markdown cells makes documentation clean and simple so notebooks are a great way to share results with colleagues. In this post I will be collecting helpful usage tips as I find them.
Make an Alias to Run the Notebook
Typing the command jupyter notebook
quickly becomes tedious so this is a good candidate for an alias.
alias nb="jupyter notebook"
Using Shell Commands
In my opinion, the ability to use shell commands inside the notebook is the best part of using notebooks. Using these tools allows you to record and reproduce all of the analysis steps exactly.
You can run any shell command within the notebook by using the !
prefix.
In[1]: !ls
Out[1]: file1.txt file2.txt file3.txt
The output of the command can be stored as a list
In[2]: filenames = !ls
In[3]: filenames
Out[3]: ['file1.txt', 'file2.txt', 'file3.txt']
Magics
%load
This magic loads a Python file from a filepath or URL and replaces the contents of the cell with the contents of the file.
%matplotlib inline
This magic places matplotlib plots inline instead of opening a new window.
%%writefile
This magic writes the contents of a cell to a file.
%pprint
This magic toggles pretty print on/off
%reset
This magic resets the namespace by removing all names defined by the user, if called without arguments.
%timeit
This magic times the execution of a Python statement or expression. The CPU and clock times are printed.
%%html
Render the cell as a block of HTML
%%latex
Render the cell as a block of latex
Adding Line Numbers
One of the most annoying things about the Jupyter Notebook is the lack of line numbers. This makes it very difficult to track down lines mentioned in error messages.
Typing CTRL-M L
toggles line numbers in the focus cell.
To make line numbers default you must edit the ~/.ipython/profile_default/static/custom/custom.js
file by adding these lines:
define([
'base/js/namespace',
'base/js/events'
],
function(IPython, events) {
events.on("app_initialized.NotebookApp",
function () {
IPython.Cell.options_default.cm_config.lineNumbers = true;
}
);
}
);
From stackoverflow
Keyboard Shortcuts
Shift-Enter
Run current cell and move to the cell below
Ctrl-Enter
Run current cell and stay in that cell
Alt-Enter
Run the current cell then create and move to a new cell below.
Subscribe via RSS


Walter is broadly interested in global biodiversity science. His
research is interdisciplinary and combines elements of biogeography,
community ecology, landscape ecology, macroecology, global change
ecology, evolutionary and comparative biology, biodiversity informatics
and conservation. Study systems include terrestrial vertebrates, in
particular birds, and plants. Work in his research group attempts to
integrate across scales of geographical, phylogenetic and ecological
organization.
Rob is a biodiversity scientist who studies spatiotemporal
changes in genetic and species diversity. I take an integrative
approach to global change biology and students in my lab range from
occupancy and to spatial ecological modelers, to landscape geneticists,
to systematists. The diversity in lab is also its strength -- we
continue to discover that the interesting questions are the integrative
ones that require multiple lines of evidence. Rob hobbies are working
and biking.
Aaron got a CS degree from Berkeley and currently works there as an
information architect. When not running or running after Noah, he
helps design and write code for MOL web apps, servers, and APIs. Aaron
enjoys reading self-introductions written in the third person.
Gaurav is a biodiversity informatician
working to make sure that biodiversity
data can make it into Map of Life smoothly
and speedily.
He has been a graduate student at
the Guralnick lab since 2011, and loves
both cats and Wikipedia.
Cody Schank
Cody has worked on GIS and spatial analysis related projects for more than a decade. His
work with the MOL and the Jetz lab builds upon his experience working on global species
distribution mapping for the Global Mammal Assessment. Cody has a passion for the combination
of modeling, remote sensing and field work for the conservation of wildlife in tropical areas.
Outside of work, Cody enjoys crossword puzzles, squash, and tracing the intrigues of Downton Abbey.
Jeremy Malczyk
Jeremy joined the Jetz Lab and the MOL team in January of 2012 with a background in geospatial
web application development. Most of his work here has been focused on user-interface development
and database management. He can also be found dragging his growing family up and down the
mountains of New England and beyond.
Carsten Meyer
Carsten is a Ph.D student working on gaps in global biodiversity knowledge and their implications
for research and conservation. Out of necessity he has recently developed a passion for automated
ways to access different biodiversity data sources and reconciling their taxonomies. Usually he
would be passionate about trekking and photography.
Peter Erb
Peter is a wildlife ecologist in the Guralnick lab working on data acquisition and its integration
into Map of Life. His work in the Guralnick lab also includes modeling the occupancy of mammals in
the Appalachian Mountain region. His interests lie primarily in answering conservation questions
through the use of GIS and citizen science. Outside of the lab, Peter works on developing novel
approaches to conservation outreach and plays in the mountains with his dog and wife.
Tom Auer
Tom blends a Master's degree in Geography from Penn State, an interest in avian distributions, and
working experience with ecological informatics to help the MOL team develop visualization and user
experience aspects of the project. In his spare time, he can be found obsessively eBirding or
honing his skills at crafting the perfect cup of coffee or tea.
Previous contributors
Andrew is working on his Ph.D. in the Guralnick lab where he has been
since the mid-2000's. Most of his work revolves around an enthusiasm
for developing automated informatics frameworks to help understand the
world's biodiversity. He has an affinity for caffiene and mostly avoids
sunlight.
Dan is a postdoc in the Jetz lab at Yale. His research integrates broad
scale spatial and phylogenetic data and uses novel techniques to tackle
questions about the evolution, distribution and conservation of biological
diversity. Dan is passionate about road cycling,
fried haloumi and the use of phylogenetic information in conservation planning.
Aimee Stewart
Aimee has worked
in the GIS and Remote Sensing fields for the last 12 years, focusing on
software development and mapping applications the University of Kansas since 2001. After
her initial seduction by proprietary software, she came to her senses and
now uses open source tools whenever possible.
John Wieczorek
John supports his habits (beach volleyball and living in the
wilderness) by balancing biodiversity informatics at UC Berkeley with
indentured servitude to the Lacey lab for field work in Argentina.
Informatics interests include biodiversity data mobilization, data
quality improvement, and georeferencing.
Map of Life Steering Committee
The Map of Life Steering Committee provides technical, managerial and scientific
advice to Map of Life and key outreach to allied communities in biodiversity, informatics,
and conservation. The committee is currently composed of:
Katrin Boehning-Gaese (Senckenberg, Frankfurt, Germany),
Simon Ferrier (GEO BON and CSIRO, Canberra Australia,
Donald Hobern (GBIF, Copenhagen, Denmark),
Jon Hoekstra (WWF. Seattle, Washington USA),
Steve Kelling (eBird, Ithaca, New York USA),
Georgina Mace (DIVERSITAS, Imperial College London, UK ),
and Henrique Pereira (GEO BON, Universidade de Lisboa, Portugal)
EOL-BioSync Working Group
Map of Life was fortunate to have many talented individuals
contribute to the early ideas, scope, and direction of the project.
Here, we list some of these contributors:
Jim Beach,
Javier de la Torre,
Ron Eastman,
Simon Ferrier,
Philip Goldstein,
Brian McGill,
Jana McPherson,
Cyndy Parr,
David Patterson,
Steven Phillips,
Thiago Rangel,
Tim Robertson,
Florencia Sangermano,
Woody Turner,
Dave Vieglais,
Mark Westneat.


HOME
PROJECTS
ABOUT
CONTACT
Some of my work
EVE Aura
Dragonforce
Galactic Travel
French Toast
Side Project