How to Deploy Container MySQL Database with Persistent Volumes in Oracle Cloud

上一篇文章,提到了如何在Oracle Cloud中创建和访问Kubernetes Cluster

接下来,这篇文章我们在这个Kubernetes Cluster中创建一个MySQL数据库,并且要使用到容器外的持久化卷来存储数据。

概念介绍

在Kubernetes的存储使用中,至少有两个概念需要了解,一个是Persistent Volumes(PV),另外一个是PersistentVolumeClaims(PVC)。我们可以这样理解,PV就像是真正的资源,就像CPU、内存一样的,只是PV是存储资源;而PVC则是任何一个容器要使用这些资源必须指定的纽带,对于容器来说,只看得到PVC。PVC在定义阶段并不会指明绑定到哪一个PV上,但是在创建之后则会由Kubernetes Controller来在有效的PV中寻找合适的绑定对象,一旦绑定,PV和PVC就形成了一对一的关系。从而,容器->PVC-PV,这样就完成了在容器中使用存储的目的。

前面说,PVC在创建之后会由Controller在有效的PV中寻找合适的绑定对象,实际上也可以在需要的时候自动创建PV然后自动绑定,这被称为Dynamic Volume Provisioning,要实现这一点,需要在Kubernetes中先定义StorageClass。StorageClass中定义了存储提供者(provisioner)还有重分配策略(reclaimPolicy)等,而provisioner实际上就是各种Kubernetes支持的存储类型,分别由各个存储厂家自己制定的,以volume plugin的方式加入到Kubernetes中,比如三大云厂商都提供了自己的存储,微软提供了Azure File和Azure Disk,AWS提供了AWS EBS,Google提供了GCE PD,另外还有其它各种存储类型,比如Ceph,Openstack Cinder,Glusterfs,ScaleIO,vSphere都有相应的provisioner。

同样Oracle Cloud也提供了自己的provisioner,并且在OKE中已经预先创建好了默认的StorageClass。

$ kubectl get StorageClass
NAME            PROVISIONER      AGE
oci (default)   oracle.com/oci   4d6h

$ kubectl describe StorageClass/oci
Name:            oci
IsDefaultClass:  Yes
Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1beta1","kind":"StorageClass","metadata":{"annotations":{"storageclass.beta.kubernetes.io/is-default-class":"true"},"name":"oci","namespace":""},"provisioner":"oracle.com/oci"}
,storageclass.beta.kubernetes.io/is-default-class=true
Provisioner:           oracle.com/oci
Parameters:            <none>
AllowVolumeExpansion:  <unset>
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>

因此,只需要在创建PVC的时候,指定StorageClass=oci,就可以实现Dynamic Volume Provisioning。

定义配置文件

配置文件的命名可以随意,这里我们起名为:pvc-dynamic.yaml

$ cat pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: "oci"
  resources:
    requests:
      storage: 50Mi

创建PVC(同时自动创建PV)

$ kubectl create -f pvc-dynamic.yaml
persistentvolumeclaim/pvc1 created

可以看到pvc1在创建以后,已经处于Bound状态,也就是已经跟PV绑定了,而VOLUME列就是自动创建的PV

$ kubectl get pvc pvc1
NAME   STATUS   VOLUME                                                                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq   50Gi       RWO            oci            73s

查看这个自动创建的PV,可以看到即使我们在配置文件中只要求了50MB,创建的PV容量却是50GB,这是Oracle Cloud中最小的PV尺寸,50GB起步。

$ kubectl describe persistentvolume/ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq
Name:            ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq
Labels:          failure-domain.beta.kubernetes.io/region=ap-tokyo-1
                 failure-domain.beta.kubernetes.io/zone=AP-TOKYO-1-AD-1
Annotations:     ociAvailabilityDomain: AP-TOKYO-1-AD-1
                 ociCompartment: ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a
                 ociProvisionerIdentity: ociProvisionerIdentity
                 ociVolumeID: ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq
                 pv.kubernetes.io/provisioned-by: oracle.com/oci
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    oci
Status:          Bound
Claim:           default/pvc1
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        50Gi
Node Affinity:   <none>
Message:
Source:
    Type:       FlexVolume (a generic volume resource that is provisioned/attached using an exec based plugin)
    Driver:     oracle/oci
    FSType:     ext4
    SecretRef:  nil
    ReadOnly:   false
    Options:    map[]
Events:         <none>

在Oracle Cloud中,自动创建的PV是以Block Storage服务中的Block Volumes方式来提供的。
Oci pvc1

在管理界面中可以看到Block Volumes的名字就是指定创建的PVC的名字:pvc1。同时还能注意到,这个卷已经Attach到一个虚拟机实例中,实际上这个实例就是Kubernetes集群中的一个Work Node。这些全部都是在一条kubectl create命令之后,Oracle Cloud自动完成的工作。

我们还可以通过oci命令行检查存储卷的情况。如何配置OCI命令行我们也在上一篇文章中提过。

$ oci bv volume get --volume-id ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq
{
  "data": {
    "availability-domain": "lOnA:AP-TOKYO-1-AD-1",
    "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a",
    "defined-tags": {},
    "display-name": "pvc1",
    "freeform-tags": {},
    "id": "ocid1.volume.oc1.ap-tokyo-1.abxhiljro7qdtft2mo7jukuhk7hvtpvtwbb6z5zrpkz55zq4lxozorfn7pwq",
    "is-hydrated": true,
    "kms-key-id": null,
    "lifecycle-state": "AVAILABLE",
    "size-in-gbs": 50,
    "size-in-mbs": 51200,
    "source-details": null,
    "system-tags": {},
    "time-created": "2019-10-21T16:09:10.178000+00:00",
    "volume-group-id": null
  },
  "etag": "432d18081fd598a1ddacddfd8982f8cb"
}

创建MySQL数据库的服务和pod

接下来就是常见的创建MySQL容器的方法了。我们在volumes中指定了pvc1(claimName: pvc1),并且将pvc1绑定的PV挂载到容器中的/var/lib/mysql目录下(mountPath: /var/lib/mysql),从而实现整个MySQL的数据文件都保存在了容器之外的持久化存储卷中。

$ cat mysql-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
  labels:
    app: mysql
spec:
    selector:
      app: mysql
    ports:
      - port: 3306
    clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:8.0
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: welcome1
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-pv-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-pv-storage
        persistentVolumeClaim:
          claimName: pvc1

$ kubectl apply -f mysql-deployment.yaml
service/mysql-service created
deployment.apps/mysql created

$ kubectl get pods --selector=app=mysql
NAME                     READY   STATUS    RESTARTS   AGE
mysql-749cb8f84b-d5qxm   1/1     Running   0          97s

登入到容器中,检查结果显示:挂载正常,数据文件创建正常。

$ kubectl exec -it mysql-749cb8f84b-d5qxm -- /bin/bash
root@mysql-749cb8f84b-d5qxm:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          39G  4.1G   35G  11% /
tmpfs            64M     0   64M   0% /dev
tmpfs           5.8G     0  5.8G   0% /sys/fs/cgroup
/dev/sda3        39G  4.1G   35G  11% /etc/hosts
shm              64M     0   64M   0% /dev/shm
/dev/sdb         50G  228M   47G   1% /var/lib/mysql
tmpfs           5.8G   12K  5.8G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           5.8G     0  5.8G   0% /proc/acpi
tmpfs           5.8G     0  5.8G   0% /proc/scsi
tmpfs           5.8G     0  5.8G   0% /sys/firmware

root@mysql-749cb8f84b-d5qxm:/# cd /var/lib/mysql
root@mysql-749cb8f84b-d5qxm:/var/lib/mysql# ls -l
total 178208
drwxr-x---. 2 mysql mysql     4096 Oct 21 16:41 #innodb_temp
-rw-r-----. 1 mysql mysql       56 Oct 21 16:41 auto.cnf
-rw-r-----. 1 mysql mysql  3084516 Oct 21 16:41 binlog.000001
-rw-r-----. 1 mysql mysql      155 Oct 21 16:41 binlog.000002
-rw-r-----. 1 mysql mysql       32 Oct 21 16:41 binlog.index
-rw-------. 1 mysql mysql     1676 Oct 21 16:41 ca-key.pem
-rw-r--r--. 1 mysql mysql     1112 Oct 21 16:41 ca.pem
-rw-r--r--. 1 mysql mysql     1112 Oct 21 16:41 client-cert.pem
-rw-------. 1 mysql mysql     1676 Oct 21 16:41 client-key.pem
-rw-r-----. 1 mysql mysql     5416 Oct 21 16:41 ib_buffer_pool
-rw-r-----. 1 mysql mysql 50331648 Oct 21 16:41 ib_logfile0
-rw-r-----. 1 mysql mysql 50331648 Oct 21 16:41 ib_logfile1
-rw-r-----. 1 mysql mysql 12582912 Oct 21 16:41 ibdata1
-rw-r-----. 1 mysql mysql 12582912 Oct 21 16:41 ibtmp1
drwx------. 2 mysql root     16384 Oct 21 16:41 lost+found
drwxr-x---. 2 mysql mysql     4096 Oct 21 16:41 mysql
-rw-r-----. 1 mysql mysql 30408704 Oct 21 16:41 mysql.ibd
drwxr-x---. 2 mysql mysql     4096 Oct 21 16:41 performance_schema
-rw-------. 1 mysql mysql     1680 Oct 21 16:41 private_key.pem
-rw-r--r--. 1 mysql mysql      452 Oct 21 16:41 public_key.pem
-rw-r--r--. 1 mysql mysql     1112 Oct 21 16:41 server-cert.pem
-rw-------. 1 mysql mysql     1676 Oct 21 16:41 server-key.pem
drwxr-x---. 2 mysql mysql     4096 Oct 21 16:41 sys
-rw-r-----. 1 mysql mysql 12582912 Oct 21 16:41 undo_001
-rw-r-----. 1 mysql mysql 10485760 Oct 21 16:41 undo_002

下一篇文章计划介绍,如何在Oracle Cloud的容器云中创建和使用共享磁盘。

How to Create and Access Container Cluster (Container Engine for Kubernetes) in Oracle Cloud

本文描述如何在Oracle Cloud中创建并访问容器服务。为了简单,所有的操作都是针对root compartment。

创建允许容器运行的Policy

这一步是必须的,否则不允许创建容器集群。
官方文档链接

左上角的产品菜单中:Governance and Administration -> Identity -> Policies
点击“Create Policy”之后,按照下图的样式创建Policy。
NewImage

创建容器集群

官方文档链接

左上角的产品菜单中:Solutions and Platform -> Developer Services -> Container Cluster(OKE)
NewImage

点击“Create Cluster”之后,页面中的元素较多,以下截图只是上半部分。
NewImage

虽然元素较多,但是在测试阶段可以几乎完全使用默认值。因此直接点“Create”就好。
在容器集群中的Worker Node可以不用ssh登录(我们在后续只需要通过oci和kubectl命令来操作集群就可以),所以页面中的PUBLIC SSH KEY确实是不用输入的,而默认的Private网络类型也无需修改。

创建容器集群需要花数分钟时间,创建成功以后,在集群页面就可以看到新创建的集群的详细信息。在我的测试里,指定了Node Pool里只有2台Node,不是默认的3台,因此在列表中只显示了两台机器。

NewImage

创建kubeconfig文件

官方文档链接

这一步比较繁琐。分为5个小步骤,不过官方文档中的描述很详尽。
NewImage

第一步:标准的生成访问密钥的过程

mkdir ~/.oci
openssl genrsa -out ~/.oci/oci_api_key.pem 2048
chmod go-rwx ~/.oci/oci_api_key.pem
openssl rsa -pubout -in ~/.oci/oci_api_key.pem -out ~/.oci/oci_api_key_public.pem
cat ~/.oci/oci_api_key_public.pem | pbcopy

第二步:在管理界面右上角的用户logo那里选择User Settings
NewImage

点击“Add Public Key”,直接将上一步生成的内容粘贴到输入框中,点“Add”。
NewImage

第三步:安装配置Oracle Cloud Infrastructure CLI
OCI CLI是一个命令行工具,就像Google Cloud也会提供gcloud命令行工具一样,这个工具用来在命令行里直接操纵Cloud中的各种资源。因为我们要用oci的create-kubeconfig命令来创建kubecongfig文件,所以需要这一步。

bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"

中间有一些需要输入的地方,也可以接受默认值一路回车。

运行完毕以后,显示successful。

-- Installation successful.
-- Run the CLI with /usr/local/bin/oci --help

安装完毕以后,必须先进行配置,才可以使用。

oci setup config

这一步配置要在Oracle Cloud的后台各个地方找到需要的信息(Tenancy UCID,User UCID,Region name),并且将值填入才可以。这里不一一赘述,在 官方文档中都有相应描述。

Enter a location for your config [/Users/Kamus/.oci/config]:
Enter a user OCID: "your user ocid here"
Enter a tenancy OCID: "your tenancy ocid here"
Enter a region (e.g. ap-mumbai-1, ap-seoul-1, ap-sydney-1, ap-tokyo-1, ca-toronto-1, eu-frankfurt-1, eu-zurich-1, sa-saopaulo-1, uk-london-1, us-ashburn-1, us-gov-ashburn-1, us-gov-chicago-1, us-gov-phoenix-1, us-langley-1, us-luke-1, us-phoenix-1): ap-tokyo-1
Do you want to generate a new RSA key pair? (If you decline you will be asked to supply the path to an existing key.) [Y/n]: n
Enter the location of your private key file: /Users/Kamus/.oci/oci_api_key.pem
Fingerprint: 5d:53:af:a8:d2:6b:4d:2f:20:24:b3:5b:c2:eb:89:86
Config written to /Users/Kamus/.oci/config

在最后一步问是否需要生成新的RSA key pair时选择n,因为我们在上面已经生成过了,这里只需要将本地的私钥地址填入即可。

这一步完毕以后,我们已经可以使用oci操纵Oracle Cloud中的资源了,比如我们列出在上面创建容器集群时自动创建的两个计算资源。

$ oci compute instance list -c “your-compartment-id”|grep "display-name"
      "display-name": "oke-cqwkmbzgbrd-nztqytbgjst-sxqablc2qgq-0",
      "display-name": "oke-cqwkmbzgbrd-nztqytbgjst-sxqablc2qgq-1",

oci命令行工具的完整文档参看这里

第四步:生成kubeconfig
oci工具只是操作Oracle Cloud中的基础资源,想要管理容器,还是要使用kubectl。为了让kubectl可以知道操纵哪里的容器集群,需要生成kubeconfig。

mkdir -p HOME/.kube
oci ce cluster create-kubeconfig --cluster-id “your-cluster-id” --file /Users/Kamus/.kube/config.oci --region ap-tokyo-1 --token-version 2.0.0 
export KUBECONFIG=HOME/.kube/config.oci

上述命令中的“your-cluster-id”可以在容器集群的信息页面中找到。
NewImage

至此,我们已经可以使用kubectl管理Oracle Cloud中的容器集群了。比如显示默认运行的所有pods。(当前,前提是已经安装了kubectl,如果还未安装,请参阅Kubernetes的官方安装文档

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                    READY   STATUS    RESTARTS   AGE
kube-system   kube-dns-7bcbdbdbcb-bdfdx               3/3     Running   0          65m
kube-system   kube-dns-7bcbdbdbcb-w8jm7               3/3     Running   0          60m
kube-system   kube-dns-autoscaler-7c6dd95548-9r9wv    1/1     Running   0          65m
kube-system   kube-flannel-ds-f2s6x                   1/1     Running   1          60m
kube-system   kube-flannel-ds-jzrjp                   1/1     Running   0          61m
kube-system   kube-proxy-btccr                        1/1     Running   0          60m
kube-system   kube-proxy-llh74                        1/1     Running   0          61m
kube-system   kubernetes-dashboard-74f74898c9-q9zpw   1/1     Running   0          65m
kube-system   proxymux-client-10.0.10.2               1/1     Running   0          60m
kube-system   proxymux-client-10.0.10.3               1/1     Running   0          61m
kube-system   tiller-deploy-6f8654fd88-xc229          1/1     Running   0          65m

总结:虽然通过kubectl管理Oracle Cloud中的容器集群前续需要的步骤较多,但是整体上而言还是比较顺畅的体验。

How to Connect SQL Developer to Oracle Autonomous Transaction Processing (ATP)

Autonomous Transaction Processing简称为ATP,是Oracle自治数据库在云上的一种表现形式,另外一种是Autonomous Data Warehouse,简称ADW。目前这两种Oracle数据库的云服务都在最新发布的Always Free Services中,也就是只需要在Oracle Cloud网站中注册一个免费用户,就可以永久免费使用一定规格的这两种服务。

实际上Oracle云的Always Free Services中包含的不仅仅是数据库,还提供了计算资源、存储资源和网络资源。使用这一整套永久免费的云服务,搭建一套完整的企业应用是OK的,完全可以用于研发环境、测试环境,甚至是最初的产品环境。
NewImage

那么具体到永久免费的数据库云服务,我们可以使用到的规格是:
1. 可以最多创建两个数据库环境,只有数据库,并不能接触到数据库在运行的主机,因为实际上只是两个PDB;
2. 每个PDB可以使用到最多1个OCPU,8GB内存,20GB存储容量;
3. 有一个额外的限制,最大的同时运行会话数不能超过20.

NewImage

具体的规格描述可以参看官方文档:https://docs.cloud.oracle.com/iaas/Content/FreeTier/resourceref.htm

好了,假设我们已经创建好了一个ATP数据库。那么首先要解决的是如何连接到这个数据库中,说Oracle是全球对于数据库安全最看重的公司其实不为过,连接Oracle ATP数据库(或者ADW数据库也一样)并没有像其它云中的数据库那么简单,只要开一个防火墙端口,给一个连接串就能连接上了。

实际上用Oracle SQL Developer连接原本是最简单的,但是会有一些错误要解决。

  • 下载最新的Oracle SQL Developer 19.2.1
  • 在Oracle Cloud管理后台下载连接ATP需要的wallet压缩文件。Oracle Cloud的UI做的是如此之复杂,入口繁多,期望你们能顺利找到这个页面。
    NewImage

点击“数据库连接”之后,会出现下载页面。
NewImage

点击“下载”按钮,会要求设置密码,实际上在使用SQL Developer连接数据库的时候并不需要此密码。因此可以随便设置。
下载的zip文件无需解压,将之放到任何一个合适的目录中即可。我放在了用户目录下的oracle目录中。

# Kamus @ Kamus-MacBook-Pro-2016 in ~/oracle [17:54:09]
$ ls *zip
Wallet_DB201909261518.zip
  • 打开SQL Developer,新增数据库连接,在连接类型中选择Cloud Wallet。如果你的SQL Developer不是最新版本,可能不是这样的称呼,所以还是请下载使用最新版本。
    NewImage
  • 如果你的机器安装的JAVA 8或者更早的版本,那么很可能会遇到这个错误。
    NewImage

点击错误中显示的链接地址,下载Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy文件,这个文件非常小,下载完毕以后解压,然后将解压出来的两个jar包:local_policy.jar和US_export_policy.jar,覆盖掉原JAVA环境中的同名文件。原来的文件位置在:$JAVA_HOME/jre/lib/security 中。比如在我的机器上,这个文件位于:

# Kamus @ Kamus-MacBook-Pro-2016 in /Library/Java/JavaVirtualMachines/jdk1.8.0_71.jdk/Contents/Home [18:08:58]
$ find . -name local_policy.jar
./jre/lib/security/local_policy.jar
  • 如果一切正常,再次打开SQL Developer并新增数据库连接,选择Cloud Wallet类型,就会出现以下界面了。
    NewImage

在(1)处直接选择在第2步中下载的wallet zip文件,在(2)处输入默认的管理用户ADMIN,和之前创建ATP服务时要求输入的ADMIN密码,期望你还记得。

  • 实际上如果我们解压wallet zip文件,就可以看到其中包含了tnsnames.ora,这其中Oracle Cloud为所有的数据库连接都预先定义好了5种连接串。
    NewImage

这5种不同的连接串,是联系到不同的resource manager计划中的,但是在我们目前初步连接数据库的时候,每种连接都是可以的。因此可以直接使用最上面的“_high”连接。

  • 保存然后连接数据库。
    NewImage

最后,ATP的官方文档链接在这里。关于连接数据库的部分在这里