2016-11-28 22:22:55 +00:00
|
|
|
---
|
|
|
|
assignees:
|
|
|
|
- bprashanth
|
|
|
|
- enisoc
|
|
|
|
- erictune
|
|
|
|
- foxish
|
|
|
|
- janetkuo
|
|
|
|
- kow3ns
|
|
|
|
- smarterclayton
|
2016-12-15 20:16:54 +00:00
|
|
|
title: StatefulSet Basics
|
2016-11-28 22:22:55 +00:00
|
|
|
---
|
|
|
|
|
|
|
|
{% capture overview %}
|
2016-12-02 22:26:22 +00:00
|
|
|
This tutorial provides an introduction to managing applications with
|
|
|
|
[StatefulSets](/docs/concepts/abstractions/controllers/statefulsets/). It
|
2016-11-28 22:22:55 +00:00
|
|
|
demonstrates how to create, delete, scale, and update the container image of a
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
{% endcapture %}
|
|
|
|
|
|
|
|
{% capture prerequisites %}
|
|
|
|
Before you begin this tutorial, you should familiarize yourself with the
|
|
|
|
following Kubernetes concepts.
|
|
|
|
|
|
|
|
* [Pods](/docs/user-guide/pods/single-container/)
|
|
|
|
* [Cluster DNS](/docs/admin/dns/)
|
|
|
|
* [Headless Services](/docs/user-guide/services/#headless-services)
|
2016-11-29 18:01:38 +00:00
|
|
|
* [PersistentVolumes](/docs/user-guide/volumes/)
|
|
|
|
* [PersistentVolume Provisioning](http://releases.k8s.io/{{page.githubbranch}}/examples/experimental/persistent-volume-provisioning/)
|
2016-12-02 22:26:22 +00:00
|
|
|
* [StatefulSets](/docs/concepts/abstractions/controllers/statefulsets/)
|
2016-11-29 19:33:21 +00:00
|
|
|
* [kubectl CLI](/docs/user-guide/kubectl)
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
This tutorial assumes that your cluster is configured to dynamically provision
|
2016-11-29 19:33:21 +00:00
|
|
|
PersistentVolumes. If your cluster is not configured to do so, you
|
2016-11-28 22:22:55 +00:00
|
|
|
will have to manually provision five 1 GiB volumes prior to starting this
|
|
|
|
tutorial.
|
|
|
|
{% endcapture %}
|
|
|
|
|
|
|
|
{% capture objectives %}
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSets are intended to be used with stateful applications and distributed
|
2016-11-28 22:22:55 +00:00
|
|
|
systems. However, the administration of stateful applications and
|
|
|
|
distributed systems on Kubernetes is a broad, complex topic. In order to
|
2016-11-29 18:01:38 +00:00
|
|
|
demonstrate the basic features of a StatefulSet, and to not conflate the former
|
2016-11-29 19:33:21 +00:00
|
|
|
topic with the latter, you will deploy a simple web application using StatefulSets.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
After this tutorial, you will be familiar with the following.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
* How to create a StatefulSet
|
|
|
|
* How a StatefulSet manages its Pods
|
|
|
|
* How to delete a StatefulSet
|
|
|
|
* How to scale a StatefulSet
|
|
|
|
* How to update the container image of a StatefulSet's Pods
|
2016-11-28 22:22:55 +00:00
|
|
|
{% endcapture %}
|
|
|
|
|
|
|
|
{% capture lessoncontent %}
|
2016-11-29 18:01:38 +00:00
|
|
|
### Creating a StatefulSet
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Begin by creating a StatefulSet using the example below. It is similar to the
|
2016-11-28 22:22:55 +00:00
|
|
|
example presented in the
|
2016-12-02 22:26:22 +00:00
|
|
|
[StatefulSets](/docs/concepts/abstractions/controllers/statefulsets/) concept. It creates
|
2016-11-28 22:22:55 +00:00
|
|
|
a [Headless Service](/docs/user-guide/services/#headless-services), `nginx`, to
|
2016-11-29 18:01:38 +00:00
|
|
|
control the domain of the StatefulSet, `web`.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
{% include code.html language="yaml" file="web.yaml" ghlink="/docs/tutorials/stateful-application/web.yaml" %}
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
Download the example above, and save it to a file named `web.yaml`
|
|
|
|
|
2016-11-28 22:22:55 +00:00
|
|
|
You will need to use two terminal windows. In the first terminal, use
|
|
|
|
[`kubectl get`](/docs/user-guide/kubectl/kubectl_get/) to watch the creation
|
2016-11-29 18:01:38 +00:00
|
|
|
of the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
In the second terminal, use
|
2016-11-29 19:33:21 +00:00
|
|
|
[`kubectl create`](/docs/user-guide/kubectl/kubectl_create/) to create the
|
2016-11-29 18:01:38 +00:00
|
|
|
Headless Service and StatefulSet defined in `web.yaml`.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl create -f web.yml
|
2016-11-28 22:22:55 +00:00
|
|
|
service "nginx" created
|
|
|
|
statefulset "web" created
|
|
|
|
```
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
The command above creates two Pods, each running an
|
2016-11-28 22:22:55 +00:00
|
|
|
[NGINX](https://www.nginx.com) webserver. Get the `nginx` Service and the
|
2016-11-29 18:01:38 +00:00
|
|
|
`web` StatefulSet to verify that they were created successfully.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get service nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
|
|
|
nginx None <none> 80/TCP 12s
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get statefulset web
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME DESIRED CURRENT AGE
|
|
|
|
web 2 1 20s
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Ordered Pod Creation
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
For a StatefulSet with N replicas, when Pods are being deployed, they are
|
2016-11-28 22:22:55 +00:00
|
|
|
created sequentially, in order from {0..N-1}. Examine the output of the
|
|
|
|
`kubectl get` command in the first terminal. Eventually, the output will
|
|
|
|
look like the example below.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 ContainerCreating 0 0s
|
|
|
|
web-0 1/1 Running 0 19s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 ContainerCreating 0 0s
|
|
|
|
web-1 1/1 Running 0 18s
|
|
|
|
```
|
|
|
|
|
|
|
|
Notice that the `web-0` Pod is launched and set to Pending prior to
|
|
|
|
launching `web-1`. In fact, `web-1` is not launched until `web-0` is
|
|
|
|
[Running and Ready](/docs/user-guide/pod-states).
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
### Pods in a StatefulSet
|
2016-12-02 22:26:22 +00:00
|
|
|
Unlike Pods in other controllers, the Pods in a StatefulSet have a unqiue
|
|
|
|
ordinal index and a stable network identity.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
#### Examining the Pod's Ordinal Index
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Get the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 1m
|
|
|
|
web-1 1/1 Running 0 1m
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
As mentioned in the [StatefulSets](/docs/concepts/abstractions/controllers/statefulsets/)
|
2016-11-29 18:01:38 +00:00
|
|
|
concept, the Pods in a StatefulSet have a sticky, unique identity. This identity
|
2016-11-28 22:22:55 +00:00
|
|
|
is based on a unique ordinal index that is assigned to each Pod by the Stateful
|
2016-11-29 19:33:21 +00:00
|
|
|
Set controller. The Pods' names take the form
|
|
|
|
`<statefulset name>-<ordinal index>`. Since the `web` StatefulSet has two
|
2016-11-28 22:22:55 +00:00
|
|
|
replicas, it creates two Pods, `web-0` and `web-1`.
|
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
#### Using Stable Network Identities
|
2016-11-28 22:22:55 +00:00
|
|
|
Each Pod has a stable hostname based on its ordinal index. Use
|
|
|
|
[`kubectl exec`](/docs/user-guide/kubectl/kubectl_exec/) to execute the
|
|
|
|
`hostname` command in each Pod.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
```
|
|
|
|
|
|
|
|
Use [`kubectl run`](/docs/user-guide/kubectl/kubectl_run/) to execute
|
|
|
|
a container that provides the `nslookup` command from the `dnsutils` package.
|
|
|
|
Using `nslookup` on the Pods' hostnames, you can examine their in-cluster DNS
|
|
|
|
addresses.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
|
|
|
|
nslookup web-0.nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
Server: 10.0.0.10
|
|
|
|
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
|
|
|
|
|
|
|
Name: web-0.nginx
|
|
|
|
Address 1: 10.244.1.6
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
nslookup web-1.nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
Server: 10.0.0.10
|
|
|
|
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
|
|
|
|
|
|
|
Name: web-1.nginx
|
|
|
|
Address 1: 10.244.2.6
|
|
|
|
```
|
|
|
|
|
|
|
|
The CNAME of the headless serivce points to SRV records (one for each Pod that
|
|
|
|
is Running and Ready). The SRV records point to A record entries that
|
|
|
|
contain the Pods' IP addresses.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal, watch the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pod -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
In a second terminal, use
|
|
|
|
[`kubectl delete`](/docs/user-guide/kubectl/kubectl_delete/) to delete all
|
2016-11-29 18:01:38 +00:00
|
|
|
the Pods in the StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete pod -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
pod "web-0" deleted
|
|
|
|
pod "web-1" deleted
|
|
|
|
```
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
Wait for the StatefulSet to restart them, and for both Pods to transition to
|
|
|
|
Running and Ready.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pod -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 0/1 ContainerCreating 0 0s
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 2s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 ContainerCreating 0 0s
|
|
|
|
web-1 1/1 Running 0 34s
|
|
|
|
```
|
|
|
|
|
|
|
|
Use `kubectl exec` and `kubectl run` to view the Pods hostnames and in-cluster
|
|
|
|
DNS entries.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
|
|
|
|
nslookup web-0.nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
Server: 10.0.0.10
|
|
|
|
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
|
|
|
|
|
|
|
Name: web-0.nginx
|
|
|
|
Address 1: 10.244.1.7
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
nslookup web-1.nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
Server: 10.0.0.10
|
|
|
|
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
|
|
|
|
|
|
|
Name: web-1.nginx
|
|
|
|
Address 1: 10.244.2.8
|
|
|
|
```
|
|
|
|
|
|
|
|
The Pods' ordinals, hostnames, SRV records, and A record names have not changed,
|
|
|
|
but the IP addresses associated with the Pods may have changed. In the cluster
|
|
|
|
used for this tutorial, they have. This is why it is important not to configure
|
2016-11-29 18:01:38 +00:00
|
|
|
other applications to connect to Pods in a StatefulSet by IP address.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-12-02 17:53:04 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
If you need to find and connect to the active members of a StatefulSet, you
|
2016-11-28 22:22:55 +00:00
|
|
|
should query the CNAME of the Headless Service
|
2016-12-02 17:53:04 +00:00
|
|
|
(`nginx.default.svc.cluster.local`). The SRV records associated with the
|
2016-11-29 18:01:38 +00:00
|
|
|
CNAME will contain only the Pods in the StatefulSet that are Running and
|
2016-11-28 22:22:55 +00:00
|
|
|
Ready.
|
|
|
|
|
2016-12-02 17:53:04 +00:00
|
|
|
If your application already implements connection logic that tests for
|
|
|
|
liveness and readiness, you can use the SRV records of the Pods (
|
|
|
|
`web-0.nginx.default.svc.cluster.local`,
|
|
|
|
`web-1.nginx.default.svc.cluster.local`), as they are stable, and your
|
|
|
|
application will be able to discover the Pods' addresses when they transition
|
|
|
|
to Running and Ready.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
#### Writing to Stable Storage
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Get the PersistentVolumeClaims for `web-0` and `web-1`.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pvc -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
|
|
|
|
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
|
|
|
|
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s
|
|
|
|
```
|
2016-11-29 18:01:38 +00:00
|
|
|
The StatefulSet controller created two PersistentVolumeClaims that are
|
2016-11-29 19:33:21 +00:00
|
|
|
bound to two [PersistentVolumes](/docs/user-guide/volumes/). As the cluster used
|
2016-11-29 18:01:38 +00:00
|
|
|
in this tutorial is configured to dynamically provision PersistentVolumes, the
|
|
|
|
PersistentVolumes were created and bound automatically.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
The NGINX webservers, by default, will serve an index file at
|
2016-11-28 22:22:55 +00:00
|
|
|
`/usr/share/nginx/html/index.html`. The `volumeMounts` field in the
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSets `spec` ensures that the `/usr/share/nginx/html` directory is
|
|
|
|
backed by a PersistentVolume.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
Write the Pods' hostnames to their `index.html` files and verify that the NGINX
|
|
|
|
webservers serve the hostnames.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal, watch the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
|
|
|
kubectl get pod -w -l app=nginx
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In a second terminal, delete all of the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete pod -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
pod "web-0" deleted
|
|
|
|
pod "web-1" deleted
|
|
|
|
```
|
|
|
|
Examine the output of the `kubectl get` command in the first terminal, and wait
|
|
|
|
for all of the Pods to transition to Running and Ready.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pod -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 0/1 ContainerCreating 0 0s
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 2s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 ContainerCreating 0 0s
|
|
|
|
web-1 1/1 Running 0 34s
|
|
|
|
```
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
Verify the web servers continue to serve their hostnames.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
```
|
|
|
|
|
|
|
|
Event though `web-0` and `web-1` were rescheduled, they continue to serve their
|
2016-11-29 18:01:38 +00:00
|
|
|
hostnames because the PersistentVolumes associated with their Persistent
|
2016-11-28 22:22:55 +00:00
|
|
|
Volume Claims are remounted to their `volumeMount`s. No matter what node `web-0`
|
2016-11-29 18:01:38 +00:00
|
|
|
and `web-1` are scheduled on, their PersistentVolumes will be mounted to the
|
2016-11-28 22:22:55 +00:00
|
|
|
appropriate mount points.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
### Scaling a StatefulSet
|
2016-12-02 22:26:22 +00:00
|
|
|
Scaling a StatefulSet refers to increasing or decreasing the number of replicas.
|
|
|
|
This is accomplished by updating the `replicas` field. You can use either
|
2016-11-28 22:22:55 +00:00
|
|
|
[`kubectl scale`](/docs/user-guide/kubectl/kubectl_scale/) or
|
|
|
|
[`kubectl patch`](/docs/user-guide/kubectl/kubectl_patch/) to scale a Stateful
|
|
|
|
Set.
|
|
|
|
|
|
|
|
#### Scaling Up
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal window, watch the Pods in the StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
In another terminal window, use `kubectl scale` to scale the number of replicas
|
|
|
|
to 5.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl scale statefulset web --replicas=5
|
2016-11-28 22:22:55 +00:00
|
|
|
statefulset "web" scaled
|
|
|
|
```
|
|
|
|
|
|
|
|
Examine the output of the `kubectl get` command in the first terminal, and wait
|
|
|
|
for the three additional Pods to transition to Running and Ready.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 2h
|
|
|
|
web-1 1/1 Running 0 2h
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-2 0/1 Pending 0 0s
|
|
|
|
web-2 0/1 Pending 0 0s
|
|
|
|
web-2 0/1 ContainerCreating 0 0s
|
|
|
|
web-2 1/1 Running 0 19s
|
|
|
|
web-3 0/1 Pending 0 0s
|
|
|
|
web-3 0/1 Pending 0 0s
|
|
|
|
web-3 0/1 ContainerCreating 0 0s
|
|
|
|
web-3 1/1 Running 0 18s
|
|
|
|
web-4 0/1 Pending 0 0s
|
|
|
|
web-4 0/1 Pending 0 0s
|
|
|
|
web-4 0/1 ContainerCreating 0 0s
|
|
|
|
web-4 1/1 Running 0 19s
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
The StatefulSet controller scaled the number of replicas. As with
|
|
|
|
[StatefulSet creation](#ordered-pod-creation), the StatefulSet controller
|
2016-11-28 22:22:55 +00:00
|
|
|
created each Pod sequentially with respect to its ordinal index, and it
|
|
|
|
waited for each Pod's predecessor to be Running and Ready before launching the
|
|
|
|
subsequent Pod.
|
|
|
|
|
|
|
|
#### Scaling Down
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal, watch the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In another terminal, use `kubectl patch` to scale the StatefulSet back down to
|
2016-11-28 22:22:55 +00:00
|
|
|
3 replicas.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
|
2016-11-28 22:22:55 +00:00
|
|
|
"web" patched
|
|
|
|
```
|
|
|
|
|
|
|
|
Wait for `web-4` and `web-3` to transition to Terminating.
|
|
|
|
|
|
|
|
```
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 3h
|
|
|
|
web-1 1/1 Running 0 3h
|
|
|
|
web-2 1/1 Running 0 55s
|
|
|
|
web-3 1/1 Running 0 36s
|
|
|
|
web-4 0/1 ContainerCreating 0 18s
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-4 1/1 Running 0 19s
|
|
|
|
web-4 1/1 Terminating 0 24s
|
|
|
|
web-4 1/1 Terminating 0 24s
|
|
|
|
web-3 1/1 Terminating 0 42s
|
|
|
|
web-3 1/1 Terminating 0 42s
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Ordered Pod Termination
|
|
|
|
|
|
|
|
The controller deleted one Pod at a time, with respect to its ordinal index,
|
2016-11-29 19:33:21 +00:00
|
|
|
in reverse order, and it waited for each to be completely shutdown before
|
|
|
|
deleting the next.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
Get the StatefulSet's PersistentVolumeClaims.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pvc -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
|
|
|
|
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 13h
|
|
|
|
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 13h
|
|
|
|
www-web-2 Bound pvc-e1125b27-b508-11e6-932f-42010a800002 1Gi RWO 13h
|
|
|
|
www-web-3 Bound pvc-e1176df6-b508-11e6-932f-42010a800002 1Gi RWO 13h
|
|
|
|
www-web-4 Bound pvc-e11bb5f8-b508-11e6-932f-42010a800002 1Gi RWO 13h
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
There are still five PersistentVolumeClaims and five PersistentVolumes.
|
2016-11-28 22:22:55 +00:00
|
|
|
When exploring a Pod's [stable storage](#stable-storage), we saw that the
|
2016-11-29 18:01:38 +00:00
|
|
|
PersistentVolumes mounted to the Pods of a StatefulSet are not deleted when
|
|
|
|
the StatefulSet's Pods are deleted. This is still true when Pod deletion is
|
|
|
|
caused by scaling the StatefulSet down. This feature can be used to facilitate
|
|
|
|
upgrading the container images of Pods in a StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-12-02 17:53:04 +00:00
|
|
|
### Updating Containers
|
|
|
|
As demonstrated in the [Scaling a StatefulSet](#scaling-a-statefulset) section,
|
|
|
|
the `replicas` field of a StatefulSet is mutable. The only other field of a
|
|
|
|
StatefulSet that can be updated is the `spec.template.containers` field.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSet currently *does not* support automated image upgrade. However, you
|
2016-11-28 22:22:55 +00:00
|
|
|
can update the `image` field of any container in the podTemplate and delete
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSet's Pods one by one, the StatefulSet controller will recreate
|
2016-11-28 22:22:55 +00:00
|
|
|
each Pod with the new image.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Patch the container image for the `web` StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.7"}]'
|
2016-11-28 22:22:55 +00:00
|
|
|
"web" patched
|
|
|
|
```
|
|
|
|
|
|
|
|
Delete the `web-0` Pod.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete pod web-0
|
2016-11-28 22:22:55 +00:00
|
|
|
pod "web-0" deleted
|
|
|
|
```
|
|
|
|
|
|
|
|
Watch `web-0`, and wait for the Pod to transition to Running and Ready.
|
|
|
|
|
|
|
|
```shell
|
|
|
|
kubectl get pod web-0 -w
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 54s
|
|
|
|
web-0 1/1 Terminating 0 1m
|
|
|
|
web-0 0/1 Terminating 0 1m
|
|
|
|
web-0 0/1 Terminating 0 1m
|
|
|
|
web-0 0/1 Terminating 0 1m
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 ContainerCreating 0 0s
|
|
|
|
web-0 1/1 Running 0 3s
|
|
|
|
```
|
|
|
|
|
|
|
|
Get the Pods to view their container images.
|
|
|
|
|
|
|
|
```shell{% raw %}
|
2016-11-29 19:33:21 +00:00
|
|
|
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
|
2016-11-28 22:22:55 +00:00
|
|
|
gcr.io/google_containers/nginx-slim:0.7
|
|
|
|
gcr.io/google_containers/nginx-slim:0.8
|
|
|
|
gcr.io/google_containers/nginx-slim:0.8
|
|
|
|
{% endraw %}```
|
|
|
|
|
|
|
|
`web-0` has had its image updated. Complete the update by deleting the remaining
|
|
|
|
Pods.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete pod web-1 web-2
|
2016-11-28 22:22:55 +00:00
|
|
|
pod "web-1" deleted
|
|
|
|
pod "web-2" deleted
|
|
|
|
```
|
|
|
|
|
|
|
|
Watch the Pods, and wait for all of them to transition to Running and Ready.
|
|
|
|
|
|
|
|
```
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 8m
|
|
|
|
web-1 1/1 Running 0 4h
|
|
|
|
web-2 1/1 Running 0 23m
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-1 1/1 Terminating 0 4h
|
|
|
|
web-1 1/1 Terminating 0 4h
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 Pending 0 0s
|
|
|
|
web-1 0/1 ContainerCreating 0 0s
|
|
|
|
web-2 1/1 Terminating 0 23m
|
|
|
|
web-2 1/1 Terminating 0 23m
|
|
|
|
web-1 1/1 Running 0 4s
|
|
|
|
web-2 0/1 Pending 0 0s
|
|
|
|
web-2 0/1 Pending 0 0s
|
|
|
|
web-2 0/1 ContainerCreating 0 0s
|
|
|
|
web-2 1/1 Running 0 36s
|
|
|
|
```
|
|
|
|
|
|
|
|
Get the Pods to view their container images.
|
|
|
|
|
|
|
|
```shell{% raw %}
|
2016-11-29 19:33:21 +00:00
|
|
|
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
|
2016-11-28 22:22:55 +00:00
|
|
|
gcr.io/google_containers/nginx-slim:0.7
|
|
|
|
gcr.io/google_containers/nginx-slim:0.7
|
|
|
|
gcr.io/google_containers/nginx-slim:0.7
|
|
|
|
{% endraw %}```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
All the Pods in the StatefulSet are now running a new container image.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
### Deleting StatefulSets
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSet supports both Non-Cascading and Cascading deletion. In a
|
|
|
|
Non-Cascading Delete, the StatefulSet's Pods are not deleted when the Stateful
|
|
|
|
Set is deleted. In a Cascading Delete, both the StatefulSet and its Pods are
|
2016-11-28 22:22:55 +00:00
|
|
|
deleted.
|
|
|
|
|
|
|
|
#### Non-Cascading Delete
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal window, watch the Pods in the StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Use [`kubectl delete`](/docs/user-guide/kubectl/kubectl_delete/) to delete the
|
2016-11-29 18:01:38 +00:00
|
|
|
StatefulSet. Make sure to supply the `--cascade=false` parameter to the
|
|
|
|
command. This parameter tells Kubernetes to only delete the StatefulSet, and to
|
2016-11-28 22:22:55 +00:00
|
|
|
not delete any of its Pods.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete statefulset web --cascade=false
|
2016-11-28 22:22:55 +00:00
|
|
|
statefulset "web" deleted
|
|
|
|
```
|
|
|
|
|
|
|
|
Get the Pods to examine their status.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 6m
|
|
|
|
web-1 1/1 Running 0 7m
|
|
|
|
web-2 1/1 Running 0 5m
|
|
|
|
```
|
|
|
|
|
|
|
|
Even though `web` has been deleted, all of the Pods are still Running and Ready.
|
|
|
|
Delete `web-0`.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete pod web-0
|
2016-11-28 22:22:55 +00:00
|
|
|
pod "web-0" deleted
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Get the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-1 1/1 Running 0 10m
|
|
|
|
web-2 1/1 Running 0 7m
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
As the `web` StatefulSet has been deleted, `web-0` has not been relaunched.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal, watch the StatefulSet's Pods.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In a second terminal, recreate the StatefulSet. Note that, unless
|
2016-11-28 22:22:55 +00:00
|
|
|
you deleted the `nginx` Service ( which you should not have ), you will see
|
|
|
|
an error indicating that the Service already exists.
|
|
|
|
|
|
|
|
```shell
|
|
|
|
kubectl create -f web.yaml
|
|
|
|
statefulset "web" created
|
|
|
|
Error from server (AlreadyExists): error when creating "web.yaml": services "nginx" already exists
|
|
|
|
```
|
|
|
|
|
|
|
|
Ignore the error. It only indicates that an attempt was made to create the nginx
|
|
|
|
Headless Service even though that Service already exists.
|
|
|
|
|
|
|
|
Examine the output of the `kubectl get` command running in the first terminal.
|
|
|
|
|
|
|
|
```shell
|
|
|
|
kubectl get pods -w -l app=nginx
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-1 1/1 Running 0 16m
|
|
|
|
web-2 1/1 Running 0 2m
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 Pending 0 0s
|
|
|
|
web-0 0/1 ContainerCreating 0 0s
|
|
|
|
web-0 1/1 Running 0 18s
|
|
|
|
web-2 1/1 Terminating 0 3m
|
|
|
|
web-2 0/1 Terminating 0 3m
|
|
|
|
web-2 0/1 Terminating 0 3m
|
|
|
|
web-2 0/1 Terminating 0 3m
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
When the `web` StatefulSet was recreated, it first relaunched `web-0`.
|
2016-11-28 22:22:55 +00:00
|
|
|
Since `web-1` was already Running and Ready, when `web-0` transitioned to
|
2016-12-02 22:26:22 +00:00
|
|
|
Running and Ready, it simply adopted this Pod. Since you recreated the StatefulSet
|
|
|
|
with `replicas` equal to 2, once `web-0` had been recreated, and once
|
2016-11-28 22:22:55 +00:00
|
|
|
`web-1` had been determined to already be Running and Ready, `web-2` was
|
|
|
|
terminated.
|
|
|
|
|
|
|
|
Let's take another look at the contents of the `index.html` file served by the
|
|
|
|
Pods' webservers.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
```
|
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
Even though you deleted both the StatefulSet and the `web-0` Pod, it still
|
2016-11-28 22:22:55 +00:00
|
|
|
serves the hostname originally entered into its `index.html` file. This is
|
2016-11-29 18:01:38 +00:00
|
|
|
because the StatefulSet never deletes the PersistentVolumes associated with a
|
|
|
|
Pod. When you recreated the StatefulSet and it relaunched `web-0`, its original
|
|
|
|
PersistentVolume was remounted.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
#### Cascading Delete
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In one terminal window, watch the Pods in the StatefulSet.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
In another terminal, delete the StatefulSet again. This time, omit the
|
2016-11-28 22:22:55 +00:00
|
|
|
`--cascade=false` parameter.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete statefulset web
|
2016-11-28 22:22:55 +00:00
|
|
|
statefulset "web" deleted
|
|
|
|
```
|
|
|
|
Examine the output of the `kubectl get` command running in the first terminal,
|
|
|
|
and wait for all of the Pods to transition to Terminating.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl get pods -w -l app=nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Running 0 11m
|
|
|
|
web-1 1/1 Running 0 27m
|
|
|
|
NAME READY STATUS RESTARTS AGE
|
|
|
|
web-0 1/1 Terminating 0 12m
|
|
|
|
web-1 1/1 Terminating 0 29m
|
|
|
|
web-0 0/1 Terminating 0 12m
|
|
|
|
web-0 0/1 Terminating 0 12m
|
|
|
|
web-0 0/1 Terminating 0 12m
|
|
|
|
web-1 0/1 Terminating 0 29m
|
|
|
|
web-1 0/1 Terminating 0 29m
|
|
|
|
web-1 0/1 Terminating 0 29m
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2016-12-02 22:26:22 +00:00
|
|
|
As you saw in the [Scaling Down](#ordered-pod-termination) section, the Pods
|
2016-11-28 22:22:55 +00:00
|
|
|
are terminated one at a time, with respect to the reverse order of their ordinal
|
2016-11-29 19:33:21 +00:00
|
|
|
indices. Before terminating a Pod, the StatefulSet controller waits for
|
2016-11-28 22:22:55 +00:00
|
|
|
the Pod's successor to be completely terminated.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Note that, while a cascading delete will delete the StatefulSet and its Pods,
|
|
|
|
it will not delete the Headless Service associated with the StatefulSet. You
|
2016-11-28 22:22:55 +00:00
|
|
|
must delete the `nginx` Service manually.
|
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete service nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
service "nginx" deleted
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Recreate the StatefulSet and Headless Service one more time.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
|
|
|
kubectl create -f web.yaml
|
|
|
|
service "nginx" created
|
|
|
|
statefulset "web" created
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
When all of the StatefulSet's Pods transition to Running and Ready, retrieve
|
2016-11-29 19:33:21 +00:00
|
|
|
the contents of their `index.html` files.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
|
2016-11-28 22:22:55 +00:00
|
|
|
web-0
|
|
|
|
web-1
|
|
|
|
```
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Even though you completely deleted the StatefulSet, and all of its Pods, the
|
|
|
|
Pods are recreated with their PersistentVolumes mounted, and `web-0` and
|
2016-11-28 22:22:55 +00:00
|
|
|
`web-1` will still serve their hostnames.
|
|
|
|
|
2016-11-29 18:01:38 +00:00
|
|
|
Finally delete the `web` StatefulSet and the `nginx` service.
|
2016-11-28 22:22:55 +00:00
|
|
|
|
|
|
|
```shell
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete service nginx
|
2016-11-28 22:22:55 +00:00
|
|
|
service "nginx" deleted
|
|
|
|
|
2016-11-29 19:33:21 +00:00
|
|
|
kubectl delete statefulset web
|
2016-11-28 22:22:55 +00:00
|
|
|
statefulset "web" deleted
|
|
|
|
```
|
|
|
|
|
|
|
|
{% endcapture %}
|
|
|
|
|
|
|
|
{% capture cleanup %}
|
2016-12-02 17:53:04 +00:00
|
|
|
You will need to delete the persistent storage media for the PersistentVolumes
|
|
|
|
used in this tutorial. Follow the necessary steps, based on your environment,
|
|
|
|
storage configuration, and provisioning method, to ensure that all storage is
|
|
|
|
reclaimed.
|
2016-11-28 22:22:55 +00:00
|
|
|
{% endcapture %}
|
|
|
|
|
|
|
|
{% include templates/tutorial.md %}
|