Posted at

Ruby on RailsをOracle Autonomous Databaseに接続する


はじめに

いまじわじわと話題になってる?Autonomous DatabaseをRailsから使うための設定方法です。

こちらのスクリプトは、Docker環境でOracle Autonomous Database(Autonomous Transaction Processing)を利用したRailsの設定となります。

「Oracle Code TokyoでRailsのハンズオンやろうぜ」と誘われ、「いいよ」とノリで答えてしまってからGKBRしておりました。

実は一年ほど前に、Oracleとの接続をためしたのですがそのときはあえなく撃沈。先にお断りしておきますが、MacOSだと事情があってこの手順そのままでは動きません。今回はOracle LinuxのDockerを利用することで比較的すんなりと接続することができました。

5/17にOracle Code Tokyoのハンズオンセッションで実施する内容とほぼ同じです。どこかのRubyの勉強会で呼んでもらえたら実演しにいきます。


Docker作成と実行

ざくっとDockerfileを書きました。これをコピペしてDockerfileという名前にしてください。

# LICENSE UPL 1.0

#
# Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved.
#
# ORACLE DOCKERFILES PROJECT
# --------------------------
#
# Dockerfile template for Oracle Instant Client
#
# HOW TO BUILD THIS IMAGE
# -----------------------
#
# Run:
# $ docker build -t oracle/instantclient:18.3.0 .
#
FROM oraclelinux:7-slim

# # docker login container-registry.oracle.com
# FROM oracle/serverjre:8

RUN curl -o /etc/yum.repos.d/public-yum-ol7.repo https://yum.oracle.com/public-yum-ol7.repo && \
yum-config-manager --enable ol7_oracle_instantclient && \
yum -y install oracle-instantclient18.3-basic oracle-instantclient18.3-devel oracle-instantclient18.3-sqlplus && \
rm -rf /var/cache/yum && \
echo /usr/lib/oracle/18.3/client64/lib > /etc/ld.so.conf.d/oracle-instantclient18.3.conf && \
ldconfig

RUN echo 'export LD_LIBRARY_PATH="/usr/lib/oracle/18.3/client64/lib"' >> ~/.bash_profile
RUN echo 'export TNS_ADMIN="/usr/local/etc"' >> ~/.bash_profile
RUN echo 'export NLS_LANG="Japanese_Japan.AL32UTF8"' >> ~/.bash_profile
RUN yum install -y git unzip vim gcc openssl-devel readline-devel zlib-devel sqlite-devel nodejs bzip2 make
RUN git clone https://github.com/sstephenson/rbenv.git /usr/local/rbenv
RUN mkdir /usr/local/rbenv/shims /usr/local/rbenv/versions /usr/local/rbenv/plugins
RUN groupadd rbenv
RUN chgrp -R rbenv /usr/local/rbenv
RUN chmod -R g+rwxXs /usr/local/rbenv
RUN git clone https://github.com/sstephenson/ruby-build.git /usr/local/rbenv/plugins/ruby-build
RUN chgrp -R rbenv /usr/local/rbenv/plugins/ruby-build
RUN chmod -R g+rwxs /usr/local/rbenv/plugins/ruby-build
RUN /usr/local/rbenv/plugins/ruby-build/install.sh
RUN git clone https://github.com/sstephenson/rbenv-default-gems.git /usr/local/rbenv/plugins/rbenv-default-gems
RUN chgrp -R rbenv /usr/local/rbenv/plugins/rbenv-default-gems
RUN chmod -R g+rwxs /usr/local/rbenv/plugins/rbenv-default-gems
RUN echo 'export RBENV_ROOT="/usr/local/rbenv"' >> /etc/profile.d/rbenv.sh
RUN echo 'export PATH="/usr/local/rbenv/bin:$PATH"' >> /etc/profile.d/rbenv.sh
RUN echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh
RUN echo '%rbenv ALL=(ALL) ALL' >> /etc/sudoers
RUN su -l root -c '/usr/local/rbenv/bin/rbenv install 2.6.3 -v'
RUN su -l root -c '/usr/local/rbenv/bin/rbenv rehash'
RUN su -l root -c '/usr/local/rbenv/bin/rbenv global 2.6.3'
RUN su -l root -c 'gem install -N rails ruby-oci8'
RUN su -l root -c 'mkdir /usr/local/app && cd /usr/local/app'
RUN su -l root -c 'rails new dummy && cd dummy && bundle package --all'

ENV PATH=$PATH:/usr/lib/oracle/18.3/client64/bin
ENV LD_LIBRARY_PATH="/usr/lib/oracle/18.3/client64/lib"
ENV TNS_ADMIN="/usr/local/etc"

CMD ["/bin/bash", "--login"]

DockerfileができたらDocker buildします。

docker build -t oracle-code-tokyo2019/rails-atp:1.0 .

docker run -it -p 3000:3000 --name oracle-ruby-atp oracle-code-tokyo/rails-atp:1.0 /bin/bash --login

OracleLinuxインスタンスで試す場合の手動構成スクリプト ここから下の構成はDockerfileでは自動化済み。実験したインスタンスに入ってたinstantclientが18.5だったのでDocker版とは微妙に異なります。


echo 'export ORACLE_HOME="/usr/lib/oracle/18.5/client64"' >> ~/.bash_profile
echo 'export LD_LIBRARY_PATH="/usr/lib/oracle/18.5/client64/lib"' >> ~/.bash_profile
echo 'export TNS_ADMIN="/usr/local/etc"' >> ~/.bash_profile
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'export NLS_LANG="Japanese_Japan.AL32UTF8"' >> ~/.bash_profile

cd; yum -y install git
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
exec $SHELL
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

一度抜ける

localpc$ docker start oracle-ruby-atp
localpc $ docker exec -it oracle-ruby-atp /bin/bash —login

sudo yum install -y gcc openssl-devel readline-devel zlib-devel sqlite-devel nodejs bzip2 make
rbenv install 2.6.3
rbenv global 2.6.3
mkdir /usr/local/app
cd /usr/local/app
gem install -N rails


Oracle Autonomous DB(ATP)接続設定


インスタンスの場合

ローカルPCからWalletのコピー

scp -i ~/.ssh/id_rsa Wallet_atp.zip opc@<innstance_ipaddr>:

ターゲットインスタンスで以下実行。sqlnnet.oraで環境変数TNS_ADMINと一致させる部分を書き換えます。

localpc#  ssh opc@<innstance_ipaddr> -i ~/.ssh/id_rsa

sudo cp Wallet_atp.zip /usr/local/etc/
cd /usr/local/etc/
sudo unzip Wallet_atp.zip
sudo cp sqlnet.ora sqlnet.ora.org && cat sqlnet.ora.org | sudo sh -c "sed -e 'N;s/\?\/network\/admin/\/usr\/local\/etc/g' > sqlnet.ora"


Dockerの場合

別ターミナルから、docker container idを確認

例 00407e328a55

docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'

docker cp Wallet_atp.zip 00407e328a55:/usr/local/etc

docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'

docker cp Wallet_atp.zip 00407e328a55:/usr/local/etc

複合スクリプト

docker cp Wallet_atp.zip `docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'`:/usr/local/etc

dockerにもどりWalletの解凍 sqlnet.oraの修正


cd /usr/local/etc/
unzip Wallet_atp.zip

cp sqlnet.ora sqlnet.ora.org && cat sqlnet.ora.org | sed -e 'N;s/\?\/network\/admin/\/usr\/local\/etc/g' > sqlnet.ora


Oracleへのコマンドからの接続確認

sqlplus admin/Oracle123456@atp_tp

Password: HogeHoge123456

つながるはずです。


Rails動作確認

まず、この状態でsqlite3を使って動作確認します。

rails new toy_app && cd toy_app/ && rails s -b 0.0.0.0

curl localhost:3000


Rails をOracle Autonomous DBで動かす


Oracle Driver のインストール

Oracleで使う場合は、activerecord-oracle_enhanced-adapter と ruby-oci8のgemをGemfileに追加してください。

# Use oracle as the database for Active Record

gem 'activerecord-oracle_enhanced-adapter', '~> 5.2.0'
gem 'ruby-oci8' # only for CRuby users

Oracle Driver追記スクリプト。めんどくさいのでコピペ一発のスクリプト化しました。

cd /usr/local/app/toy_app/

# sed -i -e "s/gem 'sqlite3'/\# gem 'sqlite3'/g" Gemfile

cat << 'EOS' | sed -i '9r /dev/stdin' Gemfile
# Oracle drivers
gem 'activerecord-oracle_enhanced-adapter', '~> 5.2.0'
gem 'ruby-oci8' # only for CRuby users
EOS

# Use oracle as the database for Active Record


Oracle Tips 10,000から始まるidを1から始まるように変更

ここで一旦うごいたのですが、idが10000から始まるという現象にノックアウト気味でした。どうやら設定が必要です。

https://doruby.jp/users/tips4tips/entries/RailsでOracle~-導入と文字列の扱いについて

Oracleのデフォルトではidが10000から始まるので、1から始まるように設定変更


cat <<EOS > config/initializers/oracle.rb
# It is recommended to set time zone in TZ environment variable so that the same timezone will be used by Ruby and by Oracle session
ENV['TZ'] = 'UTC'

ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
# true and false will be stored as 'Y' and 'N'
self.emulate_booleans_from_strings = true

# start primary key sequences from 1 (and not 10000) and take just one next value in each session
self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1"

# Use old visitor for Oracle 12c database
# self.use_old_oracle_visitor = true

# other settings ...
end
end
EOS

gemを有効化

bundle install

database.ymlを修正 tnsname.oraのtp設定を記入します。改行がはいってしまっているのは、ブロック単位で切り出して一行にnつなげてあげなければいけません。

DBユーザー名は、ここではadminにしています。実運用時にはちゃんとappユーザーを作るべきですね。

enncodingはutf-8を指定しないとひどい目に遭った気がします。

mv config/database.yml config/database.yml.org

cat <<EOS > config/database.yml
default: &default
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
adapter: oracle_enhanced
username: admin
password: HogeHoge123456
timeout: 5000
encoding: utf8

development:
<<: *default
database: (description=(address=(protocol=tcps)(port=1522)(host=adb.ap-tokyo-1.oraclecloud.com))(connect_data=(service_name=hogehogehogehoge_atp_tp.atp.oraclecloud.com))(security=(ssl_server_cert_dn="CN=adb.ap-tokyo-1.oraclecloud.com,OU=Oracle ADB TOKYO,O=Oracle Corporation,L=Redwood City,ST=California,C=US")))

test:
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
database: db/test.sqlite3

production:
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
database: db/production.sqlite3
EOS


Rails + ATPで実際にアプリを動かす

ここまで来たら、あとは普通に使うことができます。

rails generate scaffold User name:string email:string

rails db:migrate

rails s -b 0.0.0.0

お疲れ様です!


Tips


  • 順序が自動生成されているので、DB作り直しのときにSQL Developerから削除が必要でした

  • rails db:migrate:resetがエラーを吐く気がします。