How to Install Native Homebrew on an Apple Silicon M1 Mac

如果用原来的方式在M1芯片的macOS中直接安装,会报错。说Homebrew现在还不支持ARM芯片。

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Homebrew is not (yet) supported on ARM processors!
Rerun the Homebrew installer under Rosetta 2.
If you really know what you are doing and are prepared for a very broken
experience you can use another installation option for installing on ARM:
https://docs.brew.sh/Installation

要在Apple Silicon M1芯片的macOS中安装Homebrew有两种方式。

第一种:在Rosetta2下安装x86架构的Homebrew
这一种是我个人不推荐的方式,利用Rosetta2的转码功能,还是直接安装x86架构的Homebrew,后续通过这个Homebrew安装的所有软件,也将是x86架构,虽然通过Rosetta2运行在M1的macOS中也可以正常运行,但是毕竟不如直接编译成M1的ARM架构更放心。

安装方法实际上很简单,执行下面的安装命令即可。

arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

第二种:通过自行安装,运行M1 ARM架构原生的Homebrew

##首先创建安装目录
sudo mkdir -p /opt/homebrew

##将目录属主修改为当前用户,方便以后用当前用户直接brew install软件
sudo chown -R $(whoami) /opt/homebrew

##直接下载homebrew tar包并解压
curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew

##将路径增加到PATH环境变量中
如果使用的是zsh则直接修改~/.zshrc,如果使用的是bash,则修改~/.bash_profile,我的例子中修改.zshrc
echo "export PATH=/opt/homebrew/bin:$PATH" >> ~/.zshrc

##新开一个Terminal窗口或者在当前窗口让环境变量生效
source ~/.zshrc

##现在可以安装软件了,注意要使用-s选项,表示编译源码安装
brew install -s wget

安装完毕以后,我们可以通过file命令查看一下安装后的可执行文件的架构,确认确实是ARM64的原生架构。

$ which wget
/opt/homebrew/bin/wget

$ file /opt/homebrew/bin/wget
/opt/homebrew/bin/wget: Mach-O 64-bit executable arm64

Connect Your Coding (API) Skill with Your Tesla Model 3/S/X – How to Access Your Car in Your Computer

对于一个IT从业人员来说,能够通过自己的电脑来操控自己的车辆,是一件让人兴奋的事情。从此,车打破了100多年来机械产品的古板印象,摇身一变成为了充满活力的电子产品。也许这正是Tesla这类新型电动车最让人着迷的地方 – Software Define Anything,软件定义一切。

虽然特斯拉官方的API接口的说明文档,但是在Github上已经有人通过反向工程,整理并发布了一整套非官方API文档。注意只是文档是非官方的,其中描述的所有API都是特斯拉自己在iOS平台和安卓平台的官方App中使用的接口,因此这一整套API的安全性,稳定性都是可以信任的。实际上目前市面上可以看到的其它非官方用以操控Tesla车辆的应用程序,应该使用的都是这套API。

首先看一下特斯拉官方的App,在这个App里面我们可以做到如下各种操作:

IMG 4773

  • 获取车辆各种信息:包括剩余电量在内的各种车辆状态以及当前停车位置
  • 唤醒车辆
  • 启动车辆
  • 打开和关闭车内空调
  • 打开和关闭坐骑加热
  • 打开前后行李厢
  • 打开和关闭车门
  • 打开和关闭车窗
  • 让车辆鸣笛
  • 让车辆闪烁车灯
  • 打开和关闭哨兵模式

这些在官方App里面可以实现的功能,通过API也都可以通过自己写代码来实现,既然支持写代码,那么就进一步更可以做到各种智能场景,比如在iPhone手机里通过“捷径”功能,编写定时程序,让车辆在每天出门上班前的10分钟自动将车内空调打开。有了API就拥有了无穷的想象空间。

那么如何使用API来操控你的Tesla车辆,本文做简单介绍。我的车辆是Tesla Model 3,然而这套API也同样适用于Model S/X/Y,或者是Roadster,甚至是还未发售的Cybertruck。

准备条件

  1. 仔细阅读API文档,文档链接在这里
  2. 为了测试API,要从头开始写代码有些麻烦,因此我使用了Setapp订阅里包含的软件Paw,可以直接调用和测试API。同样功能的软件还有Postman

第一步:生成Token

整套API使用的是OAuth 2.0的鉴权协议,因此在第一步我们需要先生成可以用于之后调用API接口的Token,为了生成这个Token,需要提供在Tesla官网上注册的用户邮箱和密码,每个Tesla车主在购车前一定是需要在官方注册的,因此生成的Token也是每位车主独有的。注意,这个Token不要告诉任何其他人,否则别人就可以远程操控你的车辆了。当然,生成的Token是有失效期的,也可以手动失效。因此也无需担心安全性问题。

我们在Paw中创建一个Request,调用类型选择“POST”,调用的API地址是:https://owner-api.teslamotors.com/oauth/token?grant_type=password
当在Paw的地址栏中输入带?的URL时,Paw会自动将问号后的内容转换成URL Params部分的键值对。
在调用的Headers部分要填入:Content-Type: application/json,其中Content-Type是Header Name,而application/json是Header Value,如果不填入,在调用的时候会报错,而无法获得正确Token。
在调用的Body部分要填入API需要的入参,在文档中都有明确描述,分别是:

  • grant_type,这是一个固定值,就是文本password;
  • client_id,这是API提供者提供的id,也是一个固定值,是81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384;
  • client_secret,这是API提供者提供的密钥,也是一个固定值,是c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3;
  • email是上面说的Tesla官网的帐号邮箱;
  • password是上面说的Tesla官网帐号的密码。

最终整个调用在Paw中的显示如下图:
UntitledImage

可以看到这里已经成功执行了,在右侧的结果栏中已经返回了access_token,整个返回结果的JSON格式如下:

{
  "access_token": "abc123",
  "token_type": "bearer",
  "expires_in": 3888000,
  "refresh_token": "cba321",
  "created_at": 1538359034
}

Token的失效时间是45天,在失效时间内可以反复使用该Token,来进行其它的API调用。再次强调,不要将这个Token告诉别人。

第二步:获取帐号下的车辆信息

所有在这个帐号下购买的车辆都可以列出来,包括只是订购了还没有交车的车辆。我们要通过这一步获取到车辆ID,这个ID是后续要操控的车辆的标识码。

在Paw中创建一个新的Request,调用类型选择“GET”,调用的API地址是:https://owner-api.teslamotors.com/api/1/vehicles
在调用的Header部分要填入上一步获得的Token信息,键值对的表示就是:Authorization: Bearer {access_token}。
在本文的后续的步骤中也都需要提供Token,后续不再赘述。

这里需要额外注意的是:Bearer是文本,必须要写在Token前面;Token前后不要用引号括起来,否则执行调用的时候会返回报错error=”invalid_token”。

最终整个调用在Paw中的显示如下图:
UntitledImage

后续步骤中我们要使用的车辆ID是返回值中的id,而不是vehicle_id,这是需要注意的。

第三步:开始操纵车辆吧

实际上在进行完前面两步之后,剩下的就是研究API文档,探索这套API里给我们提供了哪些功能,让我们能够怎样地远程操控车辆。

唤醒车辆

后续的一些操作在执行前,必须要先唤醒车辆。否则当车辆处于休眠状态下,API调用会返回”error”:”vehicle unavailable”的报错。

在Paw中创建一个新的Request,调用类型选择“POST”,调用的API地址是:https://owner-api.teslamotors.com/api/1/vehicles/{id}/wake_up
这里的{id}就是步骤二中获取到的车辆ID。

唤醒调用成功之后,大约需要等待1-2秒钟的时间,车辆才会被真正唤醒。因此如果自己写整段代码,那么需要在wake_up调用成功以后,等待2秒钟,再调用后续的API。
唤醒调用成功以后的返回跟列出车辆信息的返回差不多。
UntitledImage

获取车辆数据

这是对于特定车辆获取更全面的数据,返回的数据比前面的列出车辆信息要多很多,包括涵盖了当前充电状态,空调状态,座椅状态,车辆配置等等各个方面的一百多项数据。

在Paw中创建一个新的Request,调用类型选择“GET”,调用的API地址是:https://owner-api.teslamotors.com/api/1/vehicles/{id}/vehicle_data
这里的{id}同样是步骤二中获取到的车辆ID。后续也不再赘述。

调用成功后返回的数据比较硬核,有兴趣的同学可以逐项阅读,以更了解自己购买的车辆。
UntitledImage

获取车辆当前位置附近的充电站信息

该调用返回的结果,跟在车内点击地图上的充电站图标之后显示出来的列表是一致的。只是目前看该调用只会返回Tesla的超充站和目的地充电站的位置,第三方合作充电站似乎并没有返回。

调用类型选择“GET”,调用的API地址是:https://owner-api.teslamotors.com/api/1/vehicles/{id}/nearby_charging_sites

返回结果中,type:destination表示是目的地充电站,type:supercharger表示是特斯拉超级充电站。在我所在的城市,名古屋只有一家超充站,远远没有中国国内普及。
UntitledImage

启动车内空调

调用类型选择“POST”,调用的API地址是:https://owner-api.teslamotors.com/api/1/vehicles/{id}/command/auto_conditioning_start

调用成功以后,返回结果”result”:true。

UntitledImage

远程调用这个请求,可能感受不到车辆的变化,那么可以打开Tesla官方App,进入到温度界面,调用成功之后,稍等2秒就可以看到这个界面自动变成了空调开启的样子。
IMG 4775

如果在调用时候,车门没有锁好,那么会返回报错:{“response”:{“reason”:”door_open”,”result”:false}},这跟在Tesla官方App中尝试启动空调获得的报错信息是一致的。
IMG 4774

总结

能够使用API来操纵车辆是让人激动的事情,这让Tesla在极客们的手里可能会爆发出各种奇葩而又颇具想象力的玩点。仔细阅读整份文档吧,去探索和体验更多编程操控车辆的乐趣。比如,将API和iOS捷径功能相结合的尝试可以参看这篇文章-【使用篇】利用iOS捷径全面控制你的特斯拉

最后,如果因为我的文章,你决定购买一辆Tesla来玩玩,那么非常欢迎使用我的推荐链接。ts.la/rakueki64678,这样你和我都能获得1500公里的免费超冲额度。

Have a good day!

How to Connect DBeaver to Oracle Autonomous Database by JDBC Thin Driver

之前介绍了如何使用SQL Developer使用PL/SQL Developer连接Oracle Autonomous Database。

但是实际上在macOS系统中,我更喜欢使用DBeaver这个GUI客户端来操作数据库,DBeaver是一款用Java编写的免费数据库工具,可以连接几十种数据库,包括RDBS,NoSQL,时序数据库,图数据库等等,非常丰富。

NewImage

在DBeaver中连接Oracle ADB,需要使用Oracle JDBC驱动。这个解决方案是在Stackoverflow中找到的,有意思的是回答者是Jeff Smith,而Jeff是Oracle SQL Developer,Oracle SQL Developer Web,Oracle SQLcl等工具的产品经理。

下载最新的JDBC驱动

需要使用18.3版本以上的JDBC驱动,可以是18.3,也可以是19.3。下载地址分别是:
Oracle Database 18c (18.3) drivers
Oracle Database 19c (19.3) drivers

我们以18.3 JDBC Driver为例,下载ojdbc8-full.tar.gz,这个压缩包中包括ojdbc8.jar等一系列使用wallet连接Oracle ATP所必须的jar包。解压以后包含以下这些文件。

$ ls -l
total 16640
-rw-r--r--@ 1 Kamus  staff     2595  8 21  2018 README.txt
-rwxr-xr-x@ 1 Kamus  staff    11596  8  3  2018 ojdbc.policy
-rw-r--r--@ 1 Kamus  staff  4161744  8  3  2018 ojdbc8.jar
-rw-r--r--@ 1 Kamus  staff   144428  8  3  2018 ons.jar
-rw-r--r--@ 1 Kamus  staff   307817  8  3  2018 oraclepki.jar
-rw-r--r--@ 1 Kamus  staff  1661545  8  3  2018 orai18n.jar
-rw-r--r--@ 1 Kamus  staff   205152  8  3  2018 osdt_cert.jar
-rw-r--r--@ 1 Kamus  staff   306854  8  3  2018 osdt_core.jar
-rw-r--r--@ 1 Kamus  staff    29103  8  3  2018 simplefan.jar
-rw-r--r--@ 1 Kamus  staff  1398331  8  3  2018 ucp.jar
-rw-r--r--@ 1 Kamus  staff   262415  8  3  2018 xdb6.jar

下载连接ADB的wallet文件

在Oracle Cloud管理后台下载连接ATP需要的wallet压缩文件,这一步在之前的文章中都有提及。
NewImage
下载以后解压到任意目录。

在Beaver中增加连接驱动

在菜单项Database->Driver Manager中,点击New按钮。
NewImage

  1. 点击“Add File”按钮,将之前下载的JDBC Driver文件ojdbc8.jar添加进来
  2. 点击“Find Class”按钮,会自动找到类名,选择类名以后,上方的Class Name处会自动填入
  3. 填写URL Template,这是最重要的一步,具体的文档介绍,可以参看Oracle官方文档-Using a JDBC URL Connection String with JDBC Thin Driver。其中的关键点是修改dbname_high为下载的ADB wallet文件解压后包括的tnsnames.ora中的连接串名字,TNS_ADMIN是ADB wallet文件解压后的目录路径
  4. 填写Default Port,这个端口对于Oracle ADB来说是1522,在tnsnames.ora中也可以看到

在DBeaver中创建数据库连接

在菜单项Database->New Database Connection中,选择上述创建的连接驱动,然后填入数据库用户名和密码。点击Test Connection。
NewImage

如果一切正常,会显示连接成功。

至此,可以成功用这个新建的连接登入到Oracle ADB中了。
NewImage

Enjoy the Oracle Cloud always free tier. 😀

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

之前写过一篇如何使用SQL Developer连接Oracle Autonomous Database,那么如果主要使用的客户端并非Oracle SQL Developer,而是PL/SQL Developer的话,该如何连接呢?

首先,下载Oracle instant client,目前的最新版本是19.3。解压到任何目录都可以。如果你的Windows机器中已经安装了Oracle数据库软件,比如Oracle Database 18c (18.3) for Microsoft Windows x64,或者是Oracle客户端,比如Oracle Database 18c Client (18.3) for Microsoft Windows x64,那么可以跳过这一步。

其次,假设你已经按照我之前的那篇文章,下载了连接Oracle自治数据库服务的wallet zip文件,需要将该zip文件解压(在使用SQL Developer连接ATP或者ADW的时候无需解压zip文件)。

解压之后的目录中 包含了以下这些文件。

PS C:\oracle\Wallet_DB201909261518> dir


    目录: C:\oracle\Wallet_DB201909261518


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019/10/12      3:40           6661 cwallet.sso
-a----       2019/10/12      3:40           6616 ewallet.p12
-a----       2019/10/12      3:40           3243 keystore.jks
-a----       2019/10/12      3:40             87 ojdbc.properties
-a----       2019/10/12     12:54            130 sqlnet.ora
-a----       2019/10/12      3:40           1776 tnsnames.ora
-a----       2019/10/12      3:40           3335 truststore.jks

需要修改sqlnet.ora文件,将WALLET_LOCATION的位置修改为解压之后的目录的绝对地址。在我的机器上,修改以后如下:

WALLET_LOCATION = (SOURCE = (METHOD = file) (METHOD_DATA = (DIRECTORY="C:\oracle\Wallet_DB201909261518")))
SSL_SERVER_DN_MATCH=yes

然后,设置Windows系统的环境变量TNS_ADMIN到wallet文件解压目录,设置方法不再赘述。设置完毕以后echo命令显示如下:

C:\oracle\Wallet_DB201909261518>echo %TNS_ADMIN%
C:\oracle\Wallet_DB201909261518

最后,启动PL/SQL Developer,新增Connection。

新增Connection时候的Database处,填写wallet文件解压目录里tnsnames.ora文件中的任何一个连接串名称。

如果你的PL/SQL Developer没有自动探测到Oracle instant client的安装位置,那么可以在配置中手工指定。

现在,可以在PL/SQL Developer中访问ATP或者ADW服务了。

Enjoy the Oracle Cloud always free tier. 😀

How to Use Shared Disk PersistentVolumes for Oracle Kubernetes Engine (OKE) in Oracle Cloud Infrastructure (OCI)

上一篇文章我们尝试了在Oracle Cloud的容器云里创建了持久化卷,并且使用该卷创建了一个MySQL数据库的容器。这样数据库里的数据就保存在了容器外部,如果容器崩溃,那么Kubernetes会根据Deployment的定义,重新创建一个容器,而数据则不会丢失。

对于企业在使用存储来说,除了上述的数据持久性保存之外,共享性也是通常会考虑的一个因素,也就是一份存储上的数据同时被多个应用共享读写。

本文阐述如何在Oracle Cloud的容器云中创建和使用共享存储,在这篇文章中我们使用NFS的方式来实现共享。

创建文件系统

首先需要在Oracle Cloud的管理后台创建一个文件系统,菜单项位于File Storage->File Systems。
NewImage

点击页面中的“Create File System”按钮,在新建文件系统的页面中点击“Edit Details”,可以对名字进行修改,实际上直接全部使用默认值也是可以的。
NewImage

为了表意更清晰,我们将文件系统的名字修改为FileSystem-for-OKE,将Export路径修改为“/oke-export”,Mount Target名称不做变化。
这些Oracle Cloud中文件系统的术语的解释,可以参看官方文档。注意的是:其中Export路径在后续的容器配置中会使用到,不过这个路径的名称跟容器中会挂载到的路径没有任何关系,可以随意命名。

修改后的并且创建成功的文件系统,可以在管理后台中看到详细情况。
NewImage

同样,通过oci命令行也可以获得创建成功的文件系统的信息。

$ oci fs file-system list \
> --compartment-id ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a \
> --availability-domain lOnA:AP-TOKYO-1-AD-1
{
  "data": [
    {
      "availability-domain": "lOnA:AP-TOKYO-1-AD-1",
      "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a",
      "defined-tags": {},
      "display-name": "FileSystem-for-OKE",
      "freeform-tags": {},
      "id": "ocid1.filesystem.oc1.ap_tokyo_1.aaaaaaaaaaaafictnzzhillqojxwiotboawxi33lpfxs2mjnmfsc2mia",
      "kms-key-id": "",
      "lifecycle-state": "ACTIVE",
      "metered-bytes": 8704,
      "time-created": "2019-10-21T13:26:40+00:00"
    }
  ]
}

在创建文件系统时,Oracle Cloud会自动创建一个Mount Target,可以在管理后台中看到详细情况。
NewImage

注意这里的IP地址,这个地址在后续创建容器持久化卷的时候也会使用到。

同样,通过oci命令行也可以获得创建成功的挂载目标的信息。

$ oci fs mount-target list \
--compartment-id ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a \
--availability-domain lOnA:AP-TOKYO-1-AD-1
{
  "data": [
    {
      "availability-domain": "lOnA:AP-TOKYO-1-AD-1",
      "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaqxee2wwzpx3s6usguambydweb2q5yfx2y5ux3lwlrpaao4gdun7a",
      "defined-tags": {},
      "display-name": "MountTarget-20191021-1322",
      "export-set-id": "ocid1.exportset.oc1.ap_tokyo_1.aaaaaa4np2snfy33nzzhillqojxwiotboawxi33lpfxs2mjnmfsc2mia",
      "freeform-tags": {},
      "id": "ocid1.mounttarget.oc1.ap_tokyo_1.aaaaaa4np2snfy34nzzhillqojxwiotboawxi33lpfxs2mjnmfsc2mia",
      "lifecycle-state": "ACTIVE",
      "private-ip-ids": [
        "ocid1.privateip.oc1.ap-tokyo-1.aaaaaaaa34v43wf2b3j73kme2rtho4rlpm6kit3avszgirkpqm5uspbqnrrq"
      ],
      "subnet-id": "ocid1.subnet.oc1.ap-tokyo-1.aaaaaaaafqsuofe2e34mnfkotolakrzktxmwdasj7dyov3u3jixqablc2qgq",
      "time-created": "2019-10-21T13:26:42+00:00"
    }
  ]
}

可以参看创建文件系统的官方文档

创建PV

创建一个PV的定义yaml文件,内容如下。server部分指定上述的Mount Target的IP地址,path部分指定上述的File System的名字。

$ cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 10.0.10.5
    path: "/oke-export"

创建PV,并检查创建结果。

$ kubectl create -f pv-nfs.yaml

$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
nfs    1Gi        RWX            Retain           Available                                   8s

$ kubectl describe pv nfs
Name:            nfs
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:
Status:          Available
Claim:           default/nfs
Reclaim Policy:  Retain
Access Modes:    RWX
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    10.0.10.5
    Path:      /oke-export
    ReadOnly:  false
Events:        <none>

可以看到现在PV的状态是Available,表示可用,在后续我们创建完PVC之后,这里会显示成Bound,表示该PV已经跟PVC绑定。

创建PVC

创建一个PVC的定义yaml文件,内容如下。

$ cat pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 1Mi

创建PVC,并检查创建结果。

$ kubectl create -f pvc-nfs.yaml

$ kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs    Bound    nfs      1Gi        RWX                           4s

$ kubectl describe pvc nfs
Name:          nfs
Namespace:     default
StorageClass:
Status:        Bound
Volume:        nfs
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      1Gi
Access Modes:  RWX
VolumeMode:    Filesystem
Events:        <none>
Mounted By:    <none>

创建容器

这里我们使用ReplicationController类型的对象来创建Pod,这样可以指定自动创建多个容器,并共享读写指定的NFS存储。
创建一个RC的定义yaml文件,内容如下。作为测试,我们使用最简单的Nginx镜像,并且指定2个复制。

$ cat rc-nfs.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: rc-nfs-test
spec:
  replicas: 2
  selector:
    app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
          - name: nginx
            containerPort: 80
        volumeMounts:
            - name: nfs
              mountPath: "/usr/share/nginx/html"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs

创建RC,并检查结果。

$ kubectl create -f rc-nfs.yaml

$ kubectl get rc rc-nfs-test
NAME          DESIRED   CURRENT   READY   AGE
rc-nfs-test   2         2         2       6m56s

$ kubectl describe replicationcontrollers/rc-nfs-test
Name:         rc-nfs-test
Namespace:    default
Selector:     app=nginx
Labels:       app=nginx
Annotations:  <none>
Replicas:     2 current / 2 desired
Pods Status:  2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:
      /usr/share/nginx/html from nfs (rw)
  Volumes:
   nfs:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  nfs
    ReadOnly:   false
Events:
  Type    Reason            Age   From                    Message
  ----    ------            ----  ----                    -------
  Normal  SuccessfulCreate  16m   replication-controller  Created pod: rc-nfs-test-bqhm9
  Normal  SuccessfulCreate  16m   replication-controller  Created pod: rc-nfs-test-8h884

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
rc-nfs-test-8h884                   1/1     Running   0          3m16s
rc-nfs-test-bqhm9                   1/1     Running   0          3m16s

到目前为止,我们已经创建好了一组(2个)共享使用磁盘的Nginx容器,共享磁盘在容器内的挂载路径是:/usr/share/nginx/html

接下来,测试一下共享读写的效果。计划在一个容器的共享磁盘中创建index.html文件,并且写入了”test sharing disk”这样的文本,之后从另外一个容器中进行查看。

首先,在其中一个容器里创建index.html文件。

$ kubectl exec rc-nfs-test-8h884 touch /usr/share/nginx/html/index.html

写入一行文本。

$ kubectl exec -it rc-nfs-test-8h884 -- /bin/bash
root@rc-nfs-test-8h884:/# cd /usr/share/nginx/html/
root@rc-nfs-test-8h884:/usr/share/nginx/html# ls
index.html
root@rc-nfs-test-8h884:/usr/share/nginx/html# echo "test sharing disk">>/usr/share/nginx/html/index.html

现在我们登录到另外一个容器中,查看是否能够读取到该内容。

$ kubectl exec rc-nfs-test-bqhm9 cat /usr/share/nginx/html/index.html
test sharing disk

Well Done!

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的官方文档链接在这里。关于连接数据库的部分在这里

How to login the VM of Docker Desktop for Mac

Docker for macOS的宿主机在哪里?

我们之前在MOVING MYSQL GROUP REPLICATION INSTANCES TO DOCKER CONTAINER ON MACOS这篇文章中提过在Docker for macOS中,容器的宿主机并不是macOS本身,而是在macOS中运行的一个虚拟机。虚拟机的路径可以通过查看Docker Desktop的配置界面获知。
NewImage

如果我们想登录这台虚拟机应该怎么做?

方法一

使用screen命令。实际上在上面那篇文章中我们提到过这个方法。
比如在上图中我们看到虚拟机的文件路径是:

/Users/Kamus/Library/Containers/com.docker.docker/Data/vms/0/Docker.raw

进入到这个文件的所在目录。可以看到tty这个软链接文件。

$ cd /Users/Kamus/Library/Containers/com.docker.docker/Data/vms/0
$ ls -l
total 31067864
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000002.000005f4
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000002.00001000
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000002.00001001
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000002.0000f3a5
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000003.000005f5
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 00000003.00000948
-rw-r--r--@ 1 Kamus  staff  63999836160  7  7 12:51 Docker.raw
-rw-r--r--  1 Kamus  staff       215040  7  4 12:22 config.iso
srwxr-xr-x  1 Kamus  staff            0  7  4 12:22 connect
lrwxr-xr-x  1 Kamus  staff           17  7  4 12:22 guest.000005f5 -> 00000003.000005f5
lrwxr-xr-x  1 Kamus  staff           17  7  4 12:22 guest.00000948 -> 00000003.00000948
-rw-r--r--  1 Kamus  staff         2303  7  4 12:22 hyperkit.json
-rw-r--r--  1 Kamus  staff            4  7  4 12:22 hyperkit.pid
drwxr-xr-x  2 Kamus  staff           64 11 21  2018 log
-rw-r--r--  1 Kamus  staff           36 11 21  2018 nic1.uuid
lrwxr-xr-x  1 Kamus  staff           12  7  4 12:22 tty -> /dev/ttys000

screen该文件即可连接到虚拟机的输出窗口中。

$ screen tty

screen之后可能终端界面会悬停,按一下Ctrl+c,即可显示出已经登录到了虚拟机中。

linuxkit-025000000001:~# uname -a
Linux linuxkit-025000000001 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 Linux

登出screen界面可以使用Ctrl+a d(同时按住Ctrl和a,然后放开再按下d),重新登入,可以使用screen -r,如果要彻底结束screen,可以使用Ctrl+a k。

方法二

还可以使用更优雅的方式,临时建一个最小化的debian容器,指定容器运行在pid=host命名空间下,然后该容器运行nsenter命令。如果之前没有安装过依赖的debian镜像,那么会首先自动下载这个镜像,镜像很小,只有101MB。

$ docker run -it --rm --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
6f2f362378c5: Pull complete
Digest: sha256:118cf8f3557e1ea766c02f36f05f6ac3e63628427ea8965fb861be904ec35a6f
Status: Downloaded newer image for debian:latest

/ # uname -a
Linux linuxkit-025000000001 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 Linux

如果说觉得101MB的debian镜像还是太大了,那么可以选择下载Alpine Linux镜像,这个镜像只有5.56MB。

$ docker run -it --rm --privileged --pid=host alpine:edge nsenter -t 1 -m -u -n -i sh

详细解释一下这条命令为什么就会登录进macOS中作为宿主机的VM里面。
–rm表示在退出的时候就自动删除该容器;
–privileged表示允许该容器访问宿主机(也就是我们想要登录的VM)中的各种设备;
–pid=host表示允许容器共享宿主机的进程命名空间(namespace),或者通俗点儿解释就是允许容器看到宿主机中的各种进程;
这些是docker在启动容器时候的参数设置,但是仅仅依靠这些参数还无法让我们直接登录到宿主机VM中,接下来解释最主要的nsenter命令。

nsenter是一个小工具允许我们进入一个指定的namespace然后运行指定的命令,ns=namespace,enter=进入。
namespace是容器技术的根基,基本上可以认为namespace就是一组隔离的资源,不同的进程可以看到不同的系统资源这里这里有比较详细的关于namespace的介绍。
可以从操作系统的/proc/[pid]/ns目录下一窥全貌。比如我们进入pid=1的ns目录下。可以看到有一共8种namespace。

# pwd
/proc/1/ns
# ls -l
total 0
lrwxrwxrwx 1 root root 0 Jul  8 12:51 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 net -> net:[4026531889]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul  8 12:51 uts -> uts:[4026531838]

接下来我们通过一个例子来直观的感受一下namespace和nsenter的功能,由于没有现成的操作系统命令可以修改一个namespace用于演示,所以我们需要先在nbrownuk下载一段c函数源码,我们演示UTS namespace,这个相对直观,所以只需要下载invoke_ns3.c即可。
UTS namespace中包含了hostname,我们的演示计划是给一个进程设置一个新的hostname,然后用nsenter进入该进程的namespace查看。

下载完invoke_ns3.c之后,需要编译。

# gcc -o invoke_ns3 ./invoke_ns3.c
# ls -l
total 28
-rwxr-xr-x 1 root root 71928 Jul  8 13:41 invoke_ns3
-rw-r--r-- 1 root root  4603 Jul  8 13:38 invoke_ns3.c

使用新的hostname启动一个bash进程,15842是执行invoke_ns3命令的进程,而15843则是bash进程,接下来我们只需要用nsenter进入15843进程验证一下即可。

# ./invoke_ns3 -vu newhostname bash
Parent: PID of parent is 15842
Parent: PID of child is 15843
 Child: PID of child is 15843
 Child: executing command bash ...
[root@newhostname root]

启动一个新的终端。可以看到在终端自己的hostname没有变化的情况下,我们用nsenter进入不同的进程,看到的hostname是不同的,其中15843进程中显示的就是上面修改过的主机名。

# hostname
ecs-arm-4xlarge
# nsenter -t 1 -u hostname
ecs-arm-4xlarge
# nsenter -t 15843 -u hostname
newhostname

最后解释一下nsenter命令的选项。回顾一下命令是:nsenter -t 1 -m -u -n -i sh
-t 1: 表示要进入哪个pid,1表示整个操作系统的主进程id
-m: 进入mount namespace,挂载点
-u: 进入UTS namespace,也就是上面我们演示的那个namespace
-n: 进入network namespace,网络
-i: 进入IPC namespace,进程间通信
sh: 表示运行/bin/sh

到此为止:docker加上–pid=host加上nsenter,就让我们登录到了在macOS中作为docker容器宿主机的VM里。

方法三

实际上跟方法二一样,但是在方法二中,需要下载至少是alpine镜像这样的最简化Linux操作系统,虽然这个镜像只有5MB多,已经很小了,但是我们还是可以直接下载网络上其它人提供的单独的nsenter镜像,这样的镜像更小,大约只有几百KB。

walkerlee/nsenter镜像有583KB。

docker run --rm -it --privileged --pid=host walkerlee/nsenter -t 1 -m -u -i -n sh

justincormack/nsenter1镜像更小,只有101KB。

docker run -it --rm --privileged --pid=host justincormack/nsenter1