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 ```