github.com/googlecloudplatform/kubernetes-workshops@v0.0.0-20180501174420-d8199445b2c3/state/README.md (about)

     1  # Storing State
     2  
     3  ## Local Docker
     4  
     5  This document is for cloud, for local docker see [local.md](local.md).
     6  
     7  ## Prerequisites
     8  
     9  * Have a cluster running and a `kubectl` binary configured to talk to
    10    that cluster
    11  * Commands assume that you have a local copy of this git repository, 
    12    and `state` is the current directory.
    13  
    14  ## Lab
    15  
    16  Create a Service for your app. This will be used for the entire Lab.
    17  
    18  ```
    19  kubectl create -f ./service-cloud.yaml
    20  ```
    21  ```
    22  service "lobsters" created
    23  ```
    24  
    25  Use the commands you have learned in previous modules to find the external IP.
    26  
    27  ### No Database
    28  
    29  Deploy the app that uses a local sqlite file:
    30  
    31  ```
    32  kubectl create -f ./dep.yaml
    33  ```
    34  ```
    35  deployment "lobsters" created
    36  ```
    37  
    38  Because [dep.yaml](dep.yaml) specifies `replicas: 5`, You have 5
    39  Lobsters pods running. The problem is that each one has it's own
    40  separate database. The app and container was not designed to scale
    41  with replicas.
    42  
    43  Try visiting the site and logging in (test/test), add a story. Refresh
    44  and it might disappear!
    45  
    46  Delete just the deployment:
    47  
    48  ```
    49  kubectl delete deployment lobsters
    50  ```
    51  ```
    52  deployment "lobsters" deleted
    53  ```
    54  
    55  ### External Database
    56  
    57  An easy way to move an existing app to Kubernetes, is to leave the
    58  database where it is. To try this out, we can start up a Lobsters pod
    59  that talks to an external MySQL database.
    60  
    61  We have a new container
    62  `gcr.io/google-samples/lobsters-db:1.0`. Source is under
    63  [lobsters-db/](lobsters-db/). This container is configured to use the
    64  environment variables `DB_HOST` and `DB_PASSWORD` to connect to a
    65  MySQL database server. The username and port are hard coded to "root"
    66  and 3306.
    67  
    68  Edit [frontend-external.yaml](frontend-external.yaml) in your favorite
    69  editor. Notice how we are setting the environment variables in the
    70  `env:` section. Change the value of the `DB_HOST` variable to an
    71  existing MySQL server, and save the file. Leave the configuration of
    72  the password variable alone, It is set up to use a Kubernetes Secret
    73  to retrieve the password. This is more secure than specifying the
    74  password in the Deployment configuration.
    75  
    76  Create the Secret using the below command. Change `mypassword` to be
    77  the actual password to the external MySQL server.
    78  
    79  ```
    80  kubectl create secret generic db-pass --from-literal=password=mypassword
    81  ```
    82  ```
    83  secret "db-pass" created
    84  ```
    85  
    86  > An alternate way to create the secret is to put the password in a
    87  > file. We'll use `pass.txt`. Caution: your editor may put a trailing
    88  > newline at the end of the file, which will not create the correct
    89  > password.
    90  > 
    91  > ```
    92  > kubectl create secret generic db-pass --from-file=password=pass.txt
    93  > ```
    94  > ```
    95  > secret "db-pass" created
    96  > ```
    97  
    98  Now you can create the Lobsters deployment that connects to the
    99  external MySQL server:
   100  
   101  ```
   102  kubectl create -f ./frontend-external.yaml
   103  ```
   104  ```
   105  deployment "lobsters" created
   106  ```
   107  
   108  About Rails: it needs the database set up using a few `rake`
   109  commands. These need to be run using the app code. To do this, we will
   110  exec a bash shell inside one of our frontend pods. First, find the
   111  name of any one of your frontend pods:
   112  
   113  ```
   114  kubectl get pods
   115  ```
   116  ```
   117  NAME                            READY     STATUS    RESTARTS   AGE
   118  lobsters-3566082729-3j2mv       1/1       Running   0          3m
   119  lobsters-3566082729-4wup5       1/1       Running   0          3m
   120  lobsters-3566082729-8cxp0       1/1       Running   0          3m
   121  lobsters-3566082729-add2d       1/1       Running   0          3m
   122  lobsters-3566082729-sepki       1/1       Running   0          3m
   123  ```
   124  
   125  Then, exec the shell.
   126  
   127  ```
   128  kubectl exec -it lobsters-3566082729-3j2mv -- /bin/bash
   129  ```
   130  ```
   131  root@lobsters-3566082729-3j2mv:/app#
   132  ```
   133  
   134  Now inside the `/app` dir, run the rake command:
   135  
   136  ```
   137  bundle exec rake db:create db:schema:load db:seed
   138  ```
   139  ```
   140  <db output>
   141  ```
   142  
   143  Then exit the shell.
   144  
   145  Check the site, now you can log in and create stories, and no matter
   146  which replica you visit, you'll see the same data.
   147  
   148  
   149  Delete the deployment to move to the next step:
   150  
   151  ```
   152  kubectl delete deployment lobsters
   153  ```
   154  ```
   155  deployment "lobsters" deleted
   156  ```
   157  
   158  ### Run MySQL in Kubernetes
   159  
   160  Look through [database.yaml](database.yaml). This config creates a
   161  MySQL Deployment and Service. It uses the standard
   162  [MySQL container](https://hub.docker.com/_/mysql/), and specifies the
   163  password to the container using the `MYSQL_ROOT_PASSWORD` environment
   164  variable, sourcing the value from the Secret you defined above. The
   165  Service name is `lobsters-sql` and will be resolvable as a hostname to
   166  any Pod in the cluster using Kube DNS. Deploy the database:
   167  
   168  ```
   169  kubectl create -f ./database.yaml
   170  ```
   171  ```
   172  service "lobsters-sql" created
   173  deployment "lobsters-sql" created
   174  ```
   175  
   176  We have [frontend-dep.yaml](frontend-dep.yaml) set up to connect to
   177  the database using the `lobsters-sql` hostname of the database
   178  Service. Because the password is sourced from the same Secret, they
   179  will always be in sync. Deploy the frontend:
   180  
   181  ```
   182  kubectl create -f ./frontend-dep.yaml
   183  ```
   184  ```
   185  deployment "lobsters" created
   186  ```
   187  
   188  This time, to run the rake commands we will re-use the `lobsters-db`
   189  container image, but specify an alternate command in the Kubernetes
   190  config. Because we only want this command to run once and exit, we use
   191  the Kubernetes Job object. See [rake-db.yaml](rake-db.yaml) for the
   192  configuration.
   193  
   194  ```
   195  kubectl create -f ./rake-db.yaml
   196  ```
   197  ```
   198  job "lobsters-rake" created
   199  ```
   200  
   201  Check the Job status:
   202  
   203  ```
   204  kubectl describe job lobsters-rake
   205  ```
   206  ```
   207  Name:		lobsters-rake
   208  Namespace:	default
   209  Image(s):	gcr.io/google-samples/lobsters-db:1.0
   210  Selector:	controller-uid=f1d48c99-186b-11e6-9e5e-42010af001a5
   211  Parallelism:	1
   212  Completions:	1
   213  Start Time:	Thu, 12 May 2016 11:04:17 -0700
   214  Labels:		app=lobsters,tier=rake
   215  Pods Statuses:	0 Running / 1 Succeeded / 0 Failed
   216  No volumes.
   217  Events:
   218    FirstSeen	LastSeen	Count	From			SubobjectPath	Type		Reason			Message
   219    ---------	--------	-----	----			-------------	--------	------			-------
   220    38s		38s		1	{job-controller }			Normal		SuccessfulCreate	Created pod: lobsters-rake-w112p
   221  ```
   222  
   223  Succeeded! Now check the app and verify it is working fine.
   224  
   225  
   226  We still have a problem with this configuration. We are running a
   227  database in Kubernetes, but we have no data persistence. The data is
   228  being stored inside the container, which is inside the Pod. This is
   229  fine as long as the container and Node continue to run. Kubernetes
   230  likes to consider Pods ephemeral and stateless. If the Node were to
   231  crash, or the Pod be deleted, Kubernetes will reschedule a new Pod for
   232  the lobsters-sql Deployment, but the data would be lost.
   233  
   234  Delete the database and service, leave the frontend running for the next step:
   235  
   236  ```
   237  kubectl delete deployment,svc lobsters-sql
   238  ```
   239  ```
   240  deployment "lobsters-sql" deleted
   241  service "lobsters-sql" deleted
   242  ```
   243  
   244  Also delete the Job.
   245  
   246  ```
   247  kubectl delete job lobsters-rake
   248  ```
   249  ```
   250  job "lobsters-rake" deleted
   251  ```
   252  
   253  ### Database with Persistent Volume
   254  
   255  To run the database inside Kubernetes, but keep the data persistent,
   256  we will use a Persistent Volume, which is a Kubernetes object that
   257  refers to an external storage device. Clouds provide resilient disks
   258  that can be easily be attached and detached to cluster nodes.
   259  
   260  > Note: For AWS see the docs to create an EBS disk and
   261  > PersistentVolume object:
   262  > http://kubernetes.io/docs/user-guide/volumes/#awselasticblockstore
   263  > http://kubernetes.io/docs/user-guide/persistent-volumes/#persistent-volumes
   264  
   265  Crate a cloud disk, make sure you use the same zone as your cluster.
   266  
   267  ```
   268  gcloud compute disks create mysql-disk --size 20GiB
   269  ```
   270  ```
   271  Created [https://www.googleapis.com/compute/v1/projects/myproject/zones/us-central1-c/disks/mysql-disk].
   272  NAME          ZONE           SIZE_GB  TYPE         STATUS
   273  mysql-disk    us-central1-c  20       pd-standard  READY
   274  ```
   275  
   276  The [cloud-pv.yaml](cloud-pv.yaml) config will create a Persistent
   277  Volume object of the `gcePersistentDisk` type that refers to the
   278  `mysql-disk' you created above.
   279  
   280  ```
   281  kubectl create -f ./cloud-pv.yaml
   282  ```
   283  ```
   284  persistentvolume "pv-1" created
   285  ```
   286  ...
   287  ```
   288  kubectl get pv
   289  ```
   290  ```
   291  NAME      CAPACITY   ACCESSMODES   STATUS      CLAIM     REASON    AGE
   292  pv-1      20Gi       RWO           Available                       6s
   293  ```
   294  ...
   295  ```
   296  kubectl describe pv pv-1
   297  ```
   298  ```
   299  Name:		pv-1
   300  Labels:		<none>
   301  Status:		Available
   302  Claim:		
   303  Reclaim Policy:	Retain
   304  Access Modes:	RWO
   305  Capacity:	20Gi
   306  Message:	
   307  Source:
   308      Type:	GCEPersistentDisk (a Persistent Disk resource in Google Compute Engine)
   309      PDName:	mysql-disk
   310      FSType:	ext4
   311      Partition:	0
   312      ReadOnly:	false
   313  ```
   314  
   315  Now take a look at [database-pvc.yaml](database-pvc.yaml). This has a
   316  new Persistent Volume Claim (PVC) object in it. A PVC will claim an
   317  existing PV in the cluster that meets its requirements. The advantage
   318  here is that our database config is independent of the cluster
   319  environment. If we used cloud disks for PVs in our cloud cluster, and
   320  iSCSI PVs in our on-prem cluster, the database config would be the
   321  same.
   322  
   323  The PVC is named `mysql-pv-claim` and is then referenced in the Pod
   324  specification and mounted to `/var/lib/mysql`. Deploy the new database:
   325  
   326  ```
   327  kubectl create -f ./database-pvc.yaml
   328  ```
   329  ```
   330  service "lobsters-sql" created
   331  persistentvolumeclaim "mysql-pv-claim" created
   332  deployment "lobsters-sql" created
   333  ```
   334  
   335  Now you can see that the PVC is bound to the PV:
   336  
   337  ```
   338  kubectl get pv
   339  ```
   340  ```
   341  NAME      CAPACITY   ACCESSMODES   STATUS    CLAIM                    REASON    AGE
   342  pv-1      20Gi       RWO           Bound     default/mysql-pv-claim             11m
   343  ```
   344  ...
   345  ```
   346  kubectl get pvc
   347  ```
   348  ```
   349  NAME             STATUS    VOLUME    CAPACITY   ACCESSMODES   AGE
   350  mysql-pv-claim   Bound     pv-1      20Gi       RWO           4m
   351  ```
   352  
   353  Re run the Job, as we have a new DB:
   354  
   355  ```
   356  kubectl create -f ./rake-db.yaml
   357  ```
   358  ```
   359  job "lobsters-rake" created
   360  ```
   361  
   362  Now visit the site and make sure it works.
   363  
   364  ## Cleanup
   365  
   366  ```
   367  kubectl delete svc,deployment,job,pvc -l app=lobsters
   368  ```
   369  ```
   370  service "lobsters" deleted
   371  service "lobsters-sql" deleted
   372  deployment "lobsters" deleted
   373  deployment "lobsters-sql" deleted
   374  job "lobsters-rake" deleted
   375  persistentvolumeclaim "mysql-pv-claim" deleted
   376  ```
   377  
   378  We didn't label the PV, as it is of general use.
   379  
   380  ```
   381  kubectl get pv
   382  ```
   383  ```
   384  NAME      CAPACITY   ACCESSMODES   STATUS     CLAIM                    REASON    AGE
   385  pv-1      20Gi       RWO           Released   default/mysql-pv-claim             22m
   386  ```
   387  
   388  You can see it is now released.
   389  
   390  ```
   391  kubectl delete pv pv-1
   392  ```
   393  ```
   394  persistentvolume "pv-1" deleted
   395  ```
   396  ...
   397  ```
   398  kubectl delete secret db-pass
   399  ```
   400  ```
   401  secret "db-pass" deleted
   402  ```
   403  
   404  Save your cloud disk for the next lab, but when you are ready to delete it:
   405  ```
   406  gcloud compute disks delete mysql-disk
   407  ```
   408  ```
   409  The following disks will be deleted. Deleting a disk is irreversible
   410  and any data on the disk will be lost.
   411   - [mysql-disk] in [us-central1-c]
   412  
   413  Do you want to continue (Y/n)?  y
   414  
   415  Deleted [https://www.googleapis.com/compute/v1/projects/myproject/zones/us-central1-c/disks/mysql-disk].
   416  ```