タイガー!タイガー!じれったいぞー!(SE編)

AS400, Java, JavaEE, JSF等の開発、習慣など。日々の気づきをまとめたブログ(備忘録)

Payara Serverアップグレードを試す

Java EE Advent Calendar 2016の16日目です。

昨日の記事は @khasunumaさんの「JavaFX から Payara Micro API を呼び出す際の注意点」でした。
明日は@backpaper0@githubさんです。



今回のゴール

  • 既存のアプリが起動しているJavaEE環境(CentOS7.2)のアップグレードを行う。
    • JDK 8.45 => JDK 8.112
    • Payara 4.1.1.154 => Payara 4.1.1.164

目的

去年の11月より、業務系サーバーに Payara を選定し、ようやくPayara管理者2年生を迎えました。
実際、運用中のPayara Severは、利用ユーザーも少なく、オンプレミス環境1台で働いてくれています。

Payaraは、3か月ごとに新しいフルリリース(バグフィックスと新機能)を提供してくれていますが、リリースしたものが安定稼働していると、なかなかアップグレードをしなくなってしまいます。

そこで、現在動いているシステムのPayaraとJavaのアップグレードをテスト機で試しておこうと思ったのが、この記事を書くきっかけとなりました。
正直、JavaEEの原理・原則を正しく理解できていないので、作業はコピペ中心です(汗)。。。

作業手順

※注:下記Linuxコマンドは、一時的な検証機での操作のため、root権限で行っています。

(1) JDKアップグレード

JDKダウンロード

現在運用のJDKは、8.45であるため、こちらも最新版にしておこうと思います。
Oracleのdownloadsサイトより、CentOS7用のlinux-x64.rpmをダウンロードします。

JDKインストール

作業前Version確認

まずは、現状確認から。1.8.0_45で間違いないようです。

# java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

# javac -version
javac 1.8.0_45
ファイル配備

とりあえず、/tmpへ配置しました。

# ls /tmp
jdk-8u112-linux-x64.rpm
インストール

rpmコマンドでインストール開始です!

# rpm -Uvh jdk-8u112-linux-x64.rpm
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:jdk1.8.0_112-2000:1.8.0_112-fcs  ################################# [100%]
Unpacking JAR files...
        tools.jar...
        plugin.jar...
        javaws.jar...
        deploy.jar...
        rt.jar...
        jsse.jar...
        charsets.jar...
        localedata.jar...

無事、インストールが終わったようです。

Java実行環境切替

J実行環境切替は、不要でした。
すでに、インストールしたばかりの「jdk1.8.0_112」が選択されているようです。

# alternatives --display java
java -ステータスは自動です。
リンクは現在 /usr/java/jdk1.8.0_112/jre/bin/java を指しています。 <<<<<<<<<<<<<<<<
/usr/java/jdk1.8.0_45/jre/bin/java - 優先項目 18045
 スレーブ ControlPanel: /usr/java/jdk1.8.0_45/jre/bin/ControlPanel
 スレーブ javaws: /usr/java/jdk1.8.0_45/jre/bin/javaws
:
:
/usr/java/jdk1.8.0_112/jre/bin/java - 優先項目 180112
 スレーブ ControlPanel: /usr/java/jdk1.8.0_112/jre/bin/ControlPanel
 スレーブ javaws: /usr/java/jdk1.8.0_112/jre/bin/javaws
:
:
現在の「最適」バージョンは /usr/java/jdk1.8.0_112/jre/bin/java です。
作業後Version確認

Version確認でも、1.8.0_112となっています。

# java -version
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)

# javac -version
javac 1.8.0_112
切替が必要な場合

もし、新しいVersionのJDKが適用されていなければ、「alternatives –config java」でjavaを指定できます。また、業務アプリが正常に動かないなど、やむを得ず戻す必要になった場合には、このコマンドで前のVersionに戻すことが可能です。

# alternatives --config java

2 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
   1           /usr/java/jdk1.8.0_45/jre/bin/java
*+ 2           /usr/java/jdk1.8.0_112/jre/bin/java

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します: 

④Payara起動確認

PayaraのWeb管理コンソールにアクセスし、「JVM Report」を確認すると、「jdk1.8.0_112」で起動していました。
オッケー、オッケー!!

java.home = /usr/java/jdk1.8.0_112/jre
java.runtime.name = Java(TM) SE Runtime Environment
java.runtime.version = 1.8.0_112-b15

(2) Payaraアップグレード

まず、今回わかったこと。
Payaraのリリース番号って、西暦下2ケタ+その年の回数ってことなんですね! はじめて利用したとき、154個目のリリースだと思ってしまいました。。。なので、現時点の最新版は164。
2016年4番目のリリースにアップグレードをしましょう。

参考にしたサイトは、こちらになります。

最初は、Web管理コンソール画面でアップグレードメニューなどあるのかなと思っていたのですが、基本的な移行方法として下記の2つが選択肢になるようです。

  1. 既存の構成をバックアップして新しいインストールに復元する
  2. 完全に分離したドメインとノードのディレクトリを維持し、新しいバージョンを既存のディレクトリに向ける

今回は、1.の方法でアップグレードの検証を行います。

①起動中Payaraの停止(payara154)

まずは、動いているPayaraサービスを止めます。
このPayaraサービスは、後述の⑦payara.service で定義済みです。

# systemctl stop payara  (/opt/payara/payara-4.1.154/payara41/bin/asadmin stop-domain)

ドメインのバックアップ(payara154)

現行のドメインドメイン名:domain1)をバックアップします。
バックアップ先は、「/opt/payara/backups」としました。
終了すると、バックアップ先に「domain1/domain1_2016_12_13_v00001.zip」というファイルが出来上がっていました(当作業は、2016/12/13に実施)。

# /opt/payara/payara-4.1.154/payara41/bin/asadmin backup-domain --backupDir /opt/payara/backups domain1
Backed up domain1 at Tue Dec 13 19:39:59 JST 2016.
Command backup-domain executed successfully.

# ls /opt/payara/backups/domain1
domain1_2016_12_13_v00001.zip

③環境構築(payara164)

次に、新環境構築です。

ファイル配備

Payaraのダウンロードページより、現時点で最新の「payara-4.1.1.164.zip」をダウンロードし、「/opt/payara/payara-4.1.164」へ配備しました。

# mkdir /opt/payara/payara-4.1.164

//ダウンロードしたファイルを配備する。

# ls /opt/payara/payara-4.1.164
payara-4.1.1.164.zip

続けて解凍処理を行います。

# unzip payara-4.1.1.164.zip
# ls /opt/payara/payara-4.1.164
payara-4.1.1.164.zip  payara41

ドメインのリストア(payara154→payara164)

②で作成されたバックアップファイルを新環境へリストアします。
バックアップディレクトリとドメイン名を指定して、無事、リストアが成功しました!!

# bin/asadmin restore-domain --backupdir /opt/payara/backups domain1
Restored the domain (domain1) to /opt/payara/payara-4.1.164/payara41/glassfish/domains/domain1
Command restore-domain executed successfully.

ドメイン所有者変更(payara164)

rootで操作してきたため、新Versionのpayara内、リストアされたドメインすべてが所有者rootになっているため、これをすでに作成済みユーザーの「payara」へ変更します。
このpayaraユーザーがpayaraサービスの実行ユーザーとなっているため、この変更は必須となります。

# chown -R payara:payara /opt/payara/payara-4.1.164

⑥起動確認(payara164)

さあ、起動してみましょう!!(テスト機 IP Address: 192.168.100.100)

# /opt/payara/payara-4.1.164/payara41/bin/asadmin start-domain
Waiting for domain1 to start ................................
Successfully started the domain : domain1
domain  Location: /opt/payara/payara-4.1.164/payara41/glassfish/domains/domain1
Log File: /var/log/payara/server.log
Admin Port: 4848
Command start-domain executed successfully.
  • https://192.168.100.100:4848

20161213193717

無事に起動できました!
当たり前なのかもしれませんが、前の設定もすべて引き継がれており、設定変更は不要でした。

※ログファイルの保管場所が同一ドメイン内である場合は、修正が必要でしょうか。

payara164停止→payara154起動テスト

念のため、payara154が動作するかもテスト。まったく問題ありません。

# /opt/payara/payara-4.1.164/payara41/bin/asadmin stop-domain
Waiting for the domain to stop .
Command stop-domain executed successfully.

# /opt/payara/payara-4.1.154/payara41/bin/asadmin start-domain
Waiting for domain1 to start ...................................
Successfully started the domain : domain1
domain  Location: /opt/payara/payara-4.1.154/payara41/glassfish/domains/domain1
Log File: /var/log/payara/server.log
Admin Port: 4848
Command start-domain executed successfully.

# /opt/payara/payara-4.1.154/payara41/bin/asadmin stop-domain
Waiting for the domain to stop .
Command stop-domain executed successfully.

なお、154と164を複数起動してみると、同じ設定のpayaraなので、ポートが被って後者が起動できませんでした。

# /opt/payara/payara-4.1.154/payara41/bin/asadmin start-domain
There is a process already using the admin port 4848 -- it probably is another instance of a GlassFish server.
Command start-domain failed.

⑦payaraサービスの編集

単一コマンドで起動/停止を確認できたので、payaraサービスの設定も変更しておきます。
「payara-4.1.154」の箇所を「payara-4.1.164」へ変更。

/lib/systemd/system/payara.service

[Unit]
Description=Payara Server
After=network.target remote-fs.target

[Service]
Type=forking
### payara-4.1.154 => payara-4.1.164
PIDFile=/opt/payara/payara-4.1.164/payara41/glassfish/domains/domain1/config/pid
ExecStart=/opt/payara/payara-4.1.164/payara41/bin/asadmin start-domain domain1
ExecReload=/opt/payara/payara-4.1.164/payara41/bin/asadmin restart-domain domain1
ExecStop=/opt/payara/payara-4.1.164/glassfish41/bin/asadmin stop-domain domain1
TimeoutStartSec=300
TimeoutStopSec=30
User=payara

[Install]
WantedBy=multi-user.target

systemdへ設定反映

# systemctl daemon-reload

⑧サービスでの起動・停止

payaraサービスで上げ下げしてみます。こちらも問題なしでした。

# systemctl start payara

# systemctl status payara
● payara.service - Payara Server
   Loaded: loaded (/usr/lib/systemd/system/payara.service; enabled; vendor preset: disabled)
   Active: active (running) since 火 2016-12-13 19:53:21 JST; 5s ago
  Process: 29867 ExecStop=/opt/payara/payara-4.1.164/glassfish41/bin/asadmin stop-domain domain1 (code=exited, status=203/EXEC)
  Process: 29894 ExecStart=/opt/payara/payara-4.1.164/payara41/bin/asadmin start-domain domain1 (code=exited, status=0/SUCCESS)
 Main PID: 29908 (java)
   CGroup: /system.slice/payara.service
           mq29908 /usr/java/jdk1.8.0_112/bin/java -cp /opt/payara/payara-4.1.164/payara41/glassfish/modules/glassfish.jar -XX:+UnlockDiagn...

12月 13 19:52:48 localhost.localdomain systemd[1]: Starting Payara Server...
12月 13 19:53:20 localhost.localdomain asadmin[29894]: Waiting for domain1 to start ...............................
12月 13 19:53:20 localhost.localdomain asadmin[29894]: Successfully started the domain : domain1
12月 13 19:53:20 localhost.localdomain asadmin[29894]: domain  Location: /opt/payara/payara-4.1.164/payara41/glassfish/domains/domain1
12月 13 19:53:20 localhost.localdomain asadmin[29894]: Log File: /var/log/payara/server.log
12月 13 19:53:20 localhost.localdomain asadmin[29894]: Admin Port: 4848
12月 13 19:53:20 localhost.localdomain asadmin[29894]: Command start-domain executed successfully.
12月 13 19:53:21 localhost.localdomain systemd[1]: Started Payara Server.

# systemctl stop payara

⑨業務アプリの動作確認

基本的なJavaEEアプリ「JSF(PrimeFaces)+CDI/EJB+JPA」で作られたWeb業務システムの動作検証を行いました。全テストは実現できておりませんが、問題なく動作できていることを確認できました。

完全テストを終えたのち、本番環境の方もJDK/Payaraのアップグレードを実施しようと思います。

これにて、今回の検証は終了となります。

まとめ

今回のpayaraのアップグレードで、主なモジュールも下記の通り、Versionアップされました。

modules 4.1.154 4.1.161 4.1.162 4.1.163 4.1.164
Eclipselink 2.6.1 2.6.2 2.6.3
Grizzly 2.3.23 2.3.24 2.3.25 2.3.28
Hazelcast 3.5.2 3.6.2 3.6.4 3.7.1
Jackson 2.5.1 2.8.1
Jersey 2.22 2.22.1 2.22.2
Mail 1.5.4 1.5.6
Mojarra 2.2.12 2.2.13
Tyrus 1.11 1.13
Weld 2.2.16.Final 2.3.2.Final 2.3.5.Final 2.4.0.Final

Payara164で起動すると、EclipseLinkのVersionも上がったことで、今まで起動時に毎回発生していた下記のヌルポも発生しなくなりました。

java.lang.NullPointerException
    at org.eclipse.persistence.platform.server.ServerPlatformUtils.createServerPlatform(ServerPlatformUtils.java:99)
    at org.eclipse.persistence.sessions.factories.SessionManager.init(SessionManager.java:77)
    at org.eclipse.persistence.sessions.factories.SessionManager.<clinit>(SessionManager.java:71)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.addSessionToGlobalSessionManager(EntityManagerSetupImpl.java:907)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.initSession(EntityManagerSetupImpl.java:2671)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:675)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate.getAbstractSession(EntityManagerFactoryDelegate.java:205)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate.createEntityManagerImpl(EntityManagerFactoryDelegate.java:305)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:337)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:318)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper._getDelegate(EntityManagerWrapper.java:197)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.find(EntityManagerWrapper.java:341)
:

今回はじめて、JavaEEのサーバーのアップグレードに挑戦したわけですが、やはり最新版で動いているという安心感があります。
もちろん、ケースバイケースかと思いますが、今後も、可能なものについては積極的に新しいVersionへ上げていきたいと思っています。

「JJUG CCC 2016 Fall」に行ってきました #jjug_ccc

日付を跨いでしまいましたが(長州力に「跨ぐなよ!、おいコラ!」と恫喝してもらいたい)、昨日、新宿で行われたJJUGに行ってきました。
2015年春、2016年春に続いて3回目。急で強引な参加になってしまいましたが、Javaエンジニア力アップのためにも、チーム開発レベルアップのためにも、何としても次の行動に結びつけるための何かを持ち帰りたい!!! そんな思いで今回も参加しました。

www.java-users.jp

基調講演1 Be a great engineer!〜 フォローすべきトレンド、スルーすべきトレンドをどう見抜くのか

まずは、朝一発目! 去年のJavaDayで初めて講演を聞かせていただいた谷本さんの話。
前回同様、お笑い満載かと思っていたら、哲学的なお話。確かに先を見越して何にウォッチすべきかってことは、7つの習慣でいうところの第2領域の時間の使い方。的外れな技術を選んでいると、結果的に緊急性の高いタスクに振り回されることにもなります。
今自分は、JSF(スルー対象)で開発をしていますが、自分たちのニーズにまさにマッチしているので、これはこれでオッケーだと思われますが、Javaフレームワークに縛られずに、同じコンポーネントベースのAngularなどのJSフレームワークも含めて3歩先を見ていこうと思っています。まあ、その前にJFS2をまだ極めきれていないので、まずはこちらを優先する必要があります。

基調講演2 Java EE - What's Next?

Oracle社のAnil Gaurさんの講演。今後のJavaEEの展望のお話。
時代は、急速にマイクロサービス化に向かっていく(いる)、そこにJavaEEもフォーカスしていくという概要か。。。
今年春から英語の勉強を再開して継続しているのですが、ほぼ聞き取れず。通訳の方、ITにも精通しているのでスラスラ翻訳し、凄すぎです! 当然、JavaEE8,9のリリースも気になりますが、次回以降、自分の英語リスニング力をアップして、本場の方の声を堪能したいところであります。

メンバーのスキルアップ、どうしてる? -Java 100本ノックで新加入メンバーを鍛えてみた-

メンバーのJava力アップに「Java100ノック」を使ってきたお話。メンバーのスキルアップには、レビューが最も大切という。さらに、レビューは、間違いを指摘するだけでなく、「よくできたことを伝える」という部分が目から鱗でした(その前に定期的なレビューができていないのですが・・・)
さらに、アンチパターンに名前を付ければいいよと。これは今日から実践できることで、自分が経験した失敗事例アンチパターンJavaに限らず)を命名しようと思います。さて、どんな風に名前を付けようか? どうせなら、覚えやすいセンスあふれる名前がいいですよね。

GitBucketを支えるJava技術とグローバルで使われるOSSの作り方

社内で、バリバリ活用させてもらっているGitBucketの歴史を聞くことができました。今回、自分が一番楽しみにしていたセッション。
GitBucketの産みの苦労話で、「こういうプロダクトを作ってみようと思う」という著者に対し、仲間から「そんなの無理だろう」というストーリー。ここで実際に優れたプロトタイプを制作し、メンバーにみてもらって「これはいける!」となったそうです。反対などにも目をくれず、行動していく姿勢があったからこそ、このOSSがあるということですね。
増井雄一郎さんの「wri.pe」同様に、まずは自分が欲しいものを創る! その精神が素晴らしいです。
自分が今一番欲しい道具っていったい何だろうか? そういう自分への問いが大切なのかもしれません。「今までこうやってきたから!」などという思考停止発言はしたくないですしね。

JVMのトラブル解決のためにやったこと~メモリー/スレッド

こちらもずっと悩ませているヒープについて。JVM、メモリ、GC、スレッドの基本の話の後、実際にやったトラブルシューティングの話がとても参考になりました。大事なのは、問題が起きてからではなく、正常時の定期観測というもの。そのツールとして、Zipkin(分散トレーシングシステム)、cactiLinuxリソース監視Tool)、Sentry(エラートラッカー)なども教えていただきましたが、まずは、はやりログ監視をしっかり実現していく必要がありそうです(できていないので)。そのツールは定番の「kibana+fluentd+elasticsearch」を習得していきたいですね。

JPA と DDD の関係で僕が思っていること

キータの「JavaEE使い方メモ」を何度も拝見させていただいています。そのページを書いている方がスピーカーということで、こちらも楽しみにしていたセッション。
自分が抱いているJPA像って、「複雑で、いまいち」というもの。その悪印象を払拭していただくことができました。JPAどうのこうのの前に、やはり大切なのはデータ構造であるということを改めて思い知る良い機会となりました。
RDB上の再設計はまだ先になりそうですが、最重要課題であることは間違いないので、今からJPAと合わせて準備します。

Payara Micro の設計と実装

組み込み系Payaraについてのお話。正直、フルPayaraを使うようになって、もうすぐ1年か。。。
Glassfishと比べてかなり安定しており、定期的にアップデートしてくれるPayara。近々、最新版にアップする計画もあるので、Payaraの中身について話を聞けてとても良かったです。Bootstrapの部分や停止する仕組みなど、そういう内部的な動きについて詳しくなることで、トラブル解決力も上がっていくでしょう。


2016年春は、JavaEE系の話がほとんどなかったのですが、今回はJavaEE系のセッションは多くあり、とても参考になりました。ただし、EEよりもSpringの方が人気と熱気があるように思えました。Springについては、まったく試していないので、こちらも自習から初めてみます。 合わせて、JPA活用、Javaスキルアップ(教育)、ヒープ管理、ログ監視とまだまだできていないことがたくさんあることを認識することができた良い機会となりました。

できる範囲で、いや無理をしてでも、ひとつずつ取り入れてやっていこうと思っています。

何のためにそれらをやるのか? 答えは簡単です。
そのシステムを使うユーザーのため。さらに継続的に管理・改善していく自分たちチームのため。その2つしかありませんね!

【AS400】レコードロック検証

改めて、DB2 for iでのレコードロックを検証していきたいと思います。

(1) レコードロックとは?

レコードロックとは、ローリングストーンズ、ビートルズ、ジミヘンなどのLPのことで・・・違いました。それはロックレコードですね。。。
テーブルのとある行をこれから書き込みを行う作業前に、その対象レコードを「今から俺が使うよ!」という宣言のようなもので、レコード(行)をロック(占有)することです。
これにより、ロックされたレコードは、他のユーザーやバッチプログラムなどからの更新処理を一時的にブロックすることができます。
書き込み直前にロックをし、レコードの値を更新して対象レコードを更新する。そんな流れになります。
ただし、ロック事態が排他制御をしてくれるわけではなく、排他制御はプログラムできちんと実装する必要があります。(JavaJPAでは、悲観的ロックや楽観的ロックという手法もあります)

(2) RPGプログラムでレコードロックを発生させてみる

それでは、実際にレコードロックを発生させてみましょう。

サンプルで下記のようなテーブルを準備しました。従業員マスタは、レコードロック検証全体で使います。部門マスタはデッドロック検証で利用します。

■ 従業員マスタ (TIGERDB/EMPLOYEES)

社員CD 苗字 名前 電話番号 部門CD
0001 山田 太郎 012-3456-7890 10
0002 山田 花子 012-3456-7891 20

■ 部門マスタ (TIGERDB/SECTIONS)

部門CD 部門名
10 営業部
20 管理部

次のような対話型RPGを作成しました。1つの対話型JOBで「社員CD=0001」をロックし、もう1つの対話型JOBで同じ「社員CD=0001」をロックしてみます(検証1の図参照)。
ちなみに、ロック呼び出しの条件は、対象テーブル(EMPLOYEES)をUモードで定義し、READ、CHAIN文などでオプション(N)を付けずに実行すれば、ロック読み取りが実施されます!

f:id:no14141:20161121170325j:plain

検証1用ソースPGM RPGLESRC/SAMPLE39

H DATEDIT(*YMD/)                                                 
F****************************************************************
F* FILE 定義                                                     
F****************************************************************
F* 従業員マスタ                                                  
FEMPLOYEES UF   E           K DISK                               
F*                                                               
D****************************************************************
D* PROTOTYPE/INTERFACE                                           
D****************************************************************
D MAIN            PR                  EXTPGM('SAMPLE39')         
D MAIN            PI                                             
D*                                                               
D****************************************************************
D* VARIABLE 定義                                                 
D****************************************************************
D LOCK_ID         S              4A   INZ(*BLANK)                
D UNLOCK_FLG      S              1A                              
D W@MSSG          S             50    INZ(*BLANK)                
D*                                                               
C****************************************************************
C* MAIN                                                          
C****************************************************************
C                   EXSR      #MAIN                              
C                   EXSR      #FINAL                             
 /FREE                                                           
  //*************************************************************
  //*  MAIN                                                      
  //*************************************************************
  BEGSR #MAIN;                                                   
     DSPLY ' レコードIDを指定して下さい  ?' '' LOCK_ID;
     EMP_CD = LOCK_ID;
     CHAIN EMP_CD RECEMP; //←読み取り成功で、レコードロック!!
     IF %FOUND();
         W@MSSG = ' レコード読み取り中 :' + LOCK_ID;
         DSPLY W@MSSG;
     ELSE;
         W@MSSG = ' レコードが存在しません :' + LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDIF;
      
   // ロック開放
      DSPLY '  ロックを開放しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK EMPLOYEES;
      ENDIF;
  ENDSR;
  //*************************************************************
  //* FINALIZE 
  //*************************************************************
  BEGSR #FINAL; 
      *INLR = *ON;
      RETURN; 
  ENDSR; 
 /END-FREE 

検証1 実行結果

従業員マスタレコード0001をUSER01のJOBがロックしている状態で、USER02がロックさせます。
すると、USER02では、初期値60秒を経過したのち、アベンド(プログラム異常終了)が発生してしまいました。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39)   
[USER01] DSPLY   レコードIDを指定して下さい  ?
[USER01] ? 0001
[USER01] DSPLY   レコード読み取り中 :0001
[USER01] DSPLY    ロックを開放しますか?  (Y/N)   ※USER01は応答しないでおく。
[USER02] CALL PGM(TIGEROBJ/SAMPLE39)
[USER02] DSPLY   レコードIDを指定して下さい  ?
[USER02] ? 0001
[USER02] ロック Waiting -  60秒(デフォルト値)
[USER02] アベンド発生!「アプリケーション・エラー。 RNX1218 は, SAMPLE39 によって
         ステートメント 0000000044 命令 X'0000' で監視されていません。」
[USER02] プログラム異常終了
[USER01] ? Y     
[USER01] プログラム正常終了

レコードロックの確認方法

ロックが発生しているかどうかは、「DSPRCDLCKコマンド」で確認が可能です。
下記は、「DSPRCDLCK FILE(TIGERDB/EMPLOYEES)」を実行した結果になります。

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400 
 ファイル . . . . . . :   EMPLOYEES       メンバー . . . . . . :   EMPLOYEES    
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER01      USER01      701737   保留     UPDATE   ←ロックしているJOB      
                  USER02      USER02      701944   待機     UPDATE   ←ロック待機中のJOB

ロックウェイト時のWRKACTJOB

                               活動ジョブの処理 
                                                      
 オプションを入力して,実行キーを押してください。
   2= 変更   3= 保留     4= 終了   5= 処理   6= 解放   7=メッセージ の表示
   8=スプール・ファイル の処理   13= 切断 ...                              
                     現行                 
 OPT  サブシステム/ジョブ  ユーザー       タイプ  CPU %  機能             状況             
      QINTER         QSYS        SBS      .0                   DEQW   
        USER01       USER01      INT      .0  PGM-SAMPLE39     DSPW  
        USER02       USER02      INT      .0  PGM-SAMPLE39     LCKW  <<<<< Lock Waitの意味   

(3) ロックが解放される条件

ロックが解放される条件は、次の通りです。

(4) レコードロックをMONITOR文でキャッチする

レコードロックのついては、例外コード1218でキャッチすることが可能です。RPG言語では、MONITORで行います。
MONITOR命令については、以前のブログでまとめていました。

検証1で使ったソースに、例外処理を加えてみます。

f:id:no14141:20161121170349j:plain

検証2用ソースPGM RPGLESRC/SAMPLE39B

 BEGSR #MAIN;                                                
   MONITOR;                                                 
       DSPLY ' レコードIDを指定して下さい  ?' '' LOCK_ID; 
       EMP_CD = LOCK_ID;                                    
       CHAIN EMP_CD RECEMP;                                 
       IF %FOUND();                                         
           W@MSSG = ' レコード読み取り中 :' + LOCK_ID;      
           DSPLY W@MSSG;                                    
       ELSE;                                                
           W@MSSG = ' レコードが存在しません :' + LOCK_ID;  
           DSPLY W@MSSG;                                    
           LEAVESR;                                         
       ENDIF;                                               
   ON-ERROR 1218;                                           
       W@MSSG = ' レコードがロックされています :' + LOCK_ID;
       DSPLY W@MSSG;                                        
       LEAVESR;                                             
   ENDMON;                                                  
                                                            
 // ロック開放                                              
     DSPLY '  ロックを開放しますか?  (Y/N)' '' UNLOCK_FLG;     
     IF UNLOCK_FLG = 'Y';                                       
         UNLOCK EMPLOYEES;                                      
     ENDIF;                                                     
 ENDSR;                                                         
 //*************************************************************
 //* FINALIZE                                                   
 //*************************************************************
 BEGSR #FINAL;                                                  
     *INLR = *ON;                                               
     RETURN;                                                    
 ENDSR;                                                         
/END-FREE                                                       

検証2 実行結果

例外処理を組み込むことで、レコードロックを例外としてキャッチすることができます。
実施に実施してみると、アベンドではなく、ログを吐いて処理を継続してくれていることがわかります。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39B)
[USER01] DSPLY   レコードIDを指定して下さい  ?
[USER01] ? 0001
[USER01] DSPLY   レコード読み取り中 :0001
[USER01] DSPLY    ロックを開放しますか?  (Y/N)   ※USER01は応答しないでおく。
[USER02] CALL PGM(TIGEROBJ/SAMPLE39B)  
[USER02] DSPLY   レコードIDを指定して下さい  ?
[USER02] ? 0001
[USER02] ロック Waiting -  60秒(デフォルト値)
[USER02] ログ出力「レコード 1 がジョブ 701737/USER01/USER01 で使用中。」
[USER02] ログ出力「ファイル EMPLOYEES のレコードを割り振ることができない。」
[USER02] DSPLY   レコードがロックされています :0001
[USER02] プログラム正常終了
[USER01] ? Y     
[USER01] プログラム正常終了
  • 例外をキャッチすれば、例外処理を記述する必要がありますが、正常終了になるため、上位からの呼び出しにも戻りコードをエラー扱いにするなどして、連携することが可能になります。

(5) デッドロックとは?

ロックといえば、「デッドロック」という概念が必ず出てきますね。
デッドロックとは、IT用語辞典を引用させていただきましょう。

デッドロックとは、複数のプロセスが互いに相手の占有している資源の解放を待ってしまい、処理が停止してしまうこと。データベースの排他制御の不備などが原因で起こる。(IT用語辞典

(6) RPGプログラムでデッドロックを発生させてみる

処理の流れは、下記の図のようになります。

20161121181628

検証PGMでは、パラメータを1で渡すと「従業員マスタ→部門マスタ」という順で呼び出してくれるのに対して、1以外の値をパラメータに渡すと、「部門マスタ→従業員マスタ」の順で呼び出されます。
すなわち、JOB別にそれぞれ別のテーブルのレコードをロックさせた状態で、次にすでにロックしているレコードを呼び合うことで、デッドロックが発生します。

検証3用ソースPGM RPGLESRC/SAMPLE39C

H DATEDIT(*YMD/) 
F****************************************************************
F* FILE 定義
F****************************************************************
F* 従業員マスタ
FEMPLOYEES UF   E           K DISK
F* 部門マスタ
FSECTIONS  UF   E           K DISK
F*
D****************************************************************
D* PROTOTYPE/INTERFACE 
D****************************************************************
D MAIN            PR                  EXTPGM('SAMPLE39C')
D                                1A   CONST
D MAIN            PI
DP@OPTION                        1A   CONST
D*
D****************************************************************
D* VARIABLE 定義
D****************************************************************
D EMP_LOCK_ID     S              4A   INZ(*BLANK)
D SEC_LOCK_ID     S              2A   INZ(*BLANK)
D UNLOCK_FLG      S              1A
D W@MSSG          S             50    INZ(*BLANK)
D*
 /FREE
  //*************************************************************
  //*  MAIN
  //*************************************************************
      IF P@OPTION = '1';
           EXSR  #SUB_EMP;
           EXSR  #SUB_SEC;
      ELSE;
           EXSR  #SUB_SEC;
           EXSR  #SUB_EMP;
      ENDIF;
      EXSR  #FINAL;
  
  //*************************************************************
  //*  従業員マスタ操作
  //*************************************************************
  BEGSR #SUB_EMP;
     DSPLY ' 従業員マスタIDを指定して下さい  ?' '' EMP_LOCK_ID;
     MONITOR;
         EMP_CD = EMP_LOCK_ID;
         CHAIN EMP_CD RECEMP;
         IF %FOUND();
             W@MSSG = ' 従業員レコード読み取り中 :' + EMP_LOCK_ID;
             DSPLY W@MSSG;
         ELSE;
             W@MSSG = ' 従業員レコードが存在しません :' + EMP_LOCK_ID;
             DSPLY W@MSSG;
             LEAVESR;
         ENDIF;
     ON-ERROR 1218;
         W@MSSG = ' 従業員レコードがロックされています :' + EMP_LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDMON;
   // ロック開放
      DSPLY '  ロックを解除しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK EMPLOYEES;
      ENDIF;
  ENDSR;
  //*************************************************************
  //*  部門マスタ操作 
  //************************************************************* 
  BEGSR #SUB_SEC;
     DSPLY ' 部門マスタIDを指定して下さい  ?' '' SEC_LOCK_ID;
     MONITOR;
         SEC_CD = SEC_LOCK_ID;
         CHAIN SEC_CD RECSEC;
         IF %FOUND();
             W@MSSG = ' 部門レコード読み取り中 :' + SEC_LOCK_ID;
             DSPLY W@MSSG;
         ELSE;
             W@MSSG = ' 部門レコードが存在しません :' + SEC_LOCK_ID;
             DSPLY W@MSSG;
             LEAVESR;
         ENDIF;
     ON-ERROR 1218;
         W@MSSG = ' 部門レコードがロックされています :' + SEC_LOCK_ID;
         DSPLY W@MSSG;
         LEAVESR;
     ENDMON;

   // ロック開放
      DSPLY '  ロックを解除しますか?  (Y/N)' '' UNLOCK_FLG;
      IF UNLOCK_FLG = 'Y';
          UNLOCK SECTIONS;
      ENDIF;
  ENDSR;
  //*************************************************************
  //* FINALIZE
  //*************************************************************
  BEGSR #FINAL;
      *INLR = *ON;
      RETURN;
  ENDSR; 
 /END-FREE

検証3 実行結果

実際にデッドロックが発生させることができました。
先に例外をキャッチしたJOBが例外処理となり、後のJOBがレコードを読み取りできるようになります。 ということは、ロックをキャッチして、再度読み取りをトライ!などという処理をループ扱いにしてしまうと、永久に待ち合うことになってしまいます(おそろしや~)。

[USER01] CALL PGM(TIGEROBJ/SAMPLE39C) PARM('1') 
[USER01] DSPLY   従業員マスタIDを指定して下さい  ?
[USER01] ? 0001 
[USER01] DSPLY   従業員レコード読み取り中 :0001
[USER01] DSPLY    ロックを解除しますか?  (Y/N) 
[USER01] ? N 
[USER02] CALL PGM(TIGEROBJ/SAMPLE39C) PARM('2')
[USER02] DSPLY   部門マスタIDを指定して下さい  ? 
[USER02] ? 10
[USER02] DSPLY   部門レコード読み取り中 :01   
[USER02] DSPLY    ロックを解除しますか?  (Y/N) 
[USER02] ? N

[USER01] DSPLY   部門マスタIDを指定して下さい  ? 
[USER01] ? 10

[USER02] DSPLY   従業員マスタIDを指定して下さい  ?
[USER02] ? 0001 

<<デッドロック発生中!!>>
↓
※先に例外をキャッチしたJOBが例外処理となり、
  後のJOBがレコードを読み取りできるようになる。
(以下省略)

DSPRCDLCK

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400
 ファイル . . . . . . :   EMPLOYEES       メンバー . . . . . . :   EMPLOYEES    
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER01      USER01      701737   保留     UPDATE              
                  USER02      USER02      701944   待機     UPDATE              
                                                                          
-----------------------------------------------------------------------------------

                       メンバー・レコード・ロックの表示                         
                                                          システム :   AS400
 ファイル . . . . . . :   SECTIONS        メンバー . . . . . . :   SECTIONS     
   ライブラリー . . . :     TIGERDB                                             
                                                                                
       レコード                                              ロック             
         番号      ジョブ      ユーザー    番号    状況      タイプ             
               1  USER02      USER02      701944   保留     UPDATE              
                  USER01      USER01      701737   待機     UPDATE         

WRKACTJOB

                               活動ジョブの処理 
                                                      
 オプションを入力して,実行キーを押してください。
   2= 変更   3= 保留     4= 終了   5= 処理   6= 解放   7=メッセージ の表示
   8=スプール・ファイル の処理   13= 切断 ...                              
                     現行                 
 OPT  サブシステム/ジョブ  ユーザー       タイプ  CPU %  機能             状況             
      QINTER         QSYS        SBS      .0                   DEQW    
        USER01       USER01      INT      .0  PGM-SAMPLE39C    LCKW             
        USER02       USER02      INT      .0  PGM-SAMPLE39C    LCKW

デッドロックを発生させない定石として、トランザクション処理は、一定の順序でテーブルアクセスを行う」という点と、「読み取り成功するまでループ扱いにしない(例外処理を組み込む)」という2点でしょうか。

(7) 最大レコード待機時間の変更

DB2 for iでは、テーブル構築時に、最大レコード待機時間を指定できます。
デフォルト値が、60秒となっておりますので、よほどの事情が無ければこのままでいいように思います。

すでに運用中のテーブルの場合には、CHGPFコマンドで変更は可能です(取扱い注意! 私の環境では、CHGPF実行後にRPGのリコンパイルは不要でした)。
下記のコマンドでは、最大レコード待機時間を10秒に設定するケースです。

CHGPF FILE(TIGERDB/EMPLOYEES) WAITRCD(10)

(8) SQLでのロック制御

では、SQL文でのロックはどうするのか? ということで、DB2 for iでは「FOR UPDATE WITH 分離レベル」でロック処理ができました。

SELECT * FROM TIGERDB/EMPLOYEES 
 WHERE EMP_CD = '0001'  
   FOR UPDATE WITH RS    

分離レベルについては、データがアクセスされている間、 データが他のプロセスからどのようにロックされ分離されるかを決めるもので、複数指定ができます。きつめのロック、軽めのロックという具合でしょうか・・・。
こちらはJavaEEの場合、APサーバー側で設定し、EJBで自動トランザクション処理を行っているため、個別での操作は経験不足です。
いずれにしましても気をつけたい点は、COMMIT or ROLLBACKを実行するまで、ロックされ続けている点と、WHERE文によっては大量のレコードをロックしてしまう点でしょうか。

以上で、レコードロック検証についてのまとめ終了です。

【AS400】SQLRPGLE-挿入、更新、削除件数を取得する

久しぶりにAS400のSQLRPGLEネタです。

SQLのINSERT文、UPDATE文、DELETE文実行後に、どうやって結果レコード数を取得するのか? という疑問が湧きました。

Javaであれば、下記のようにSQL実行結果の戻り値で結果レコード件数を取得できるのですが・・・。

int updatedCount = stmt.executeUpdate(sql);

SQLCA(SQL Communications area)

SQLRPGLEでは、SQLCA(SQL Communications area)と呼ばれる領域の中から、更新レコード件数を取得できるようです。
SQLCAとは、プログラムとRDMSの間で、統計情報とエラー情報を共有するメモリ領域で、SQLRPGLEは変数定義せずに、SQLCAの変数を参照することができました。

具体的な変数は、SQLERRD配列の3要素目。

SQLにより更新、挿入、削除されたレコード数を「SQLERRD(3)」で取り出します。

その結果を戻り値などで返すパターンが実用的でありましょう。

サンプルSQLRPGLE

EXEC SQL                           
    DELETE TABLE_NAME                  
     WHERE COMPLETED = '1'            
       AND CRT_DATE <= :W@YYMD;      
                                   
CNT = %DEC(SQLERRD(3):7:0);     

【JSF2.2】セッションごとに保持できるView画面件数が25画面までに制限されている

1年半ほど前にブログで、ViewExpiredExceptionに苦しんだ記録をまとめましたが、再度、似たようなトラブルに直面してしまいました。
タイトルがその原因だったわけですが、この問題についてもまとめておこうと思います。

STATE_SAVING_METHODについて

JavaEE7のWebアプリケーションでは、STATE_SAVING_METHODの設定で、クライアントごとのView情報をどこで保管するかを管理します。
デフォルトでは、serverになっており、Viewの数も15と少な目に設定されています。(下記の設定は、定義しなくてもデフォルトで反映されます)

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>sever</param-value>
    </context-param>

    <context-param>
        <param-name>com.sun.faces.numberOfViewsInSession</param-name>
        <param-value>15</param-value>
    </context-param>

    <context-param>
        <param-name>com.sun.faces.numberOfLogicalViews</param-name>
        <param-value>15</param-value>
    </context-param>

前回、JavaEEアプリの設定(web.xml)で、「javax.faces.STATE_SAVING_METHOD」を「server」から「client」に変更することで、view情報保持数の制限がなくなりました。(ただし、セキュリティ的に危険な状態の設定ではあります。)

今回のトラブル内容

今回起きたトラブルは、STATE_SAVING_METHODを「client」にしているのも関わらず、数ヵ月に1~2回、view情報が消失してしまうというもの。
テスト機で再現させようとトライするも、なかなか発生せず・・・。いったい何故???
ユーザー側にも、きちんとした原因を説明することができず、もどかしい状態が続いておりました。

ちなみに、利用しているソフトウェア構成は下記の通りです。

ソフトウェア構成

  • JavaEE7(Payara 4.1.1.154)
    • JSF2.2
    • Primefaces5.3

原因発見

かなり、困惑させられましたが、PrimeFacesの「Dialog Framework」で作成した検索画面を25回連続で呼び出して、呼び出し元画面に戻ってきたときに、呼び出し元画面のView Scope情報が消失されました!

stackoverflowでも、下記の記事を発見することができ、25画面情報だけが保持されているという仮説が正しいことを確認しました。

stackoverflow.com

では、なぜ「Dialog Framework」が1画面扱いでカウントされてしまうのか?
PrimeFacesのDialog Frameworkでは、基本的にiframe(HTMLのタグの一つで、Webページ内に矩形の領域を設け別のページなどを読み込んで表示する)タグの中で他のビューを見せているようで、それ自身のviewスコープを持っています。
Dialog Framework画面で、特定のモデルを選択しても、Close扱いでDialogを終わらせても、結果的にはDialog Frameworkのview情報が残ってしまいます。
セッションタイムが終了したタイミングで、viewも合わせて消失されます。ということは、セッションが生きている間、view情報が生きていることになります。どんだけ~♪

「Dialog Framework」を初めて知ったときには、これで疎結合にシンプルな開発が実現できるぞ!と思っておったのですが、こういう問題が潜伏していたとは!(ただし、こういう部分をきちんと事前検証・調査しておくことが、自分のミッションではありました。。。反省!)

改善策

stackoverflow.com

この辺のサイトを確認しますと、デフォルトで「p:dialog」を推奨するとありました。 「p:dialog」では呼び出し元ページと同じview scope内に存在するので、view数を食いつぶしてしまうことはなくなりそうです。

ただし、現時点で大量の「Dialog Framework」のviewを作成しており、ひとつずつ、複合コンポーネント化するなどして、各種検索画面を「p:dialog」へ切り替えていく必要がありそうです。

また、view情報の保持する設定を拡張できないかと調査したのですが、こちらの情報はJSF2.2では設定不可っぽいのです。
次回のJSF2.3では反映されるでしょうか。

[JAVASERVERFACES-4015] Provide documentation and tuning options for activeViewMaps - Java.net JIRA

いずれにしましても、1人が25画面も同時に別タブで使うことなどありえませんので、やはりコツコツと「p:dialog」へ移行していくほうが最善策かと思っております。

今回は、かなり骨が折れましたが、原因がわかって、かなりスッキリできました!

【EXCEL】 データの出現数を集計する

完全に個人用の備忘録です。

Excelでとあるデータの出現数をカウントして表にまとめたいシーンを想定し、作業手順に落としました。

ピボットテーブルを使って集計できました。

作業手順

サンプルデータを準備しました。A2からA51まで(計50件)のデータとなっています。

f:id:no14141:20160913200651j:plain

(1)ピボットテーブルの作成

挿入メニューから「ピボットテーブル」を選択。

f:id:no14141:20160913200701j:plain

レポートの出力先はお好みですが、今回は新規シート向けで設定!

f:id:no14141:20160913200708j:plain

(2)ピボットテーブルの設計

フィールドを行のゾーンにドラッグ&ドロップしますと・・・

f:id:no14141:20160913200714j:plain

データの実際の値がグループ化されて、ラベルとして出力されました。

f:id:no14141:20160913200721j:plain

次に、フィールドより右クリックで、「値に追加」を選択します。

f:id:no14141:20160913200729j:plain

すると、初期値の値は「合計」として集計されます。

f:id:no14141:20160913200735j:plain

最後に、値フィールドの設定で、「合計」→「データの個数」へ変更します。

f:id:no14141:20160913200740j:plain

f:id:no14141:20160913200746j:plain

(3)完成

無事に、データ出現数をカウントした表が完成しました!!

f:id:no14141:20160913202242j:plain

競馬の人気別単勝率、連対率などで使えそうですね。

Docker for Windows(Ver1.12)を試してみました

今年初め、10年ぶりに資格取得に挑戦しようと、LPIC1へ挑戦しました。

何とか合格することができ、「よっしゃー!」と当時は気持ちが高ぶったのですが、半年近くも経つと、あの時の気持ちの高ぶりはすっかり冷めてしまっておりました。

これではいかん!!

内的モチベーションを働かせ、約5年ぶりにPCも購入!(初Let's Note。MacからまたWindowsに逆戻りです)

次は、LPIC2に挑戦だ!と息巻き、PCにVirtualBox(5.1.4)とVagrant(1.8.5)をインストールして実機でLinuxの勉強に取り掛かろうとしたその瞬間・・・

f:id:no14141:20160827080658j:plain

何度やっても、PCがクラッシュ(ログはKernel-Power41のみ)。
VirtualBox単体で試しても、同様の結果だったので、どうやら、VirtualBoxの仮想OS起動時に発生しているようです。

これは、困った。。。俺はただLinuxコマンドを勉強したいだけなのに。

Windows10 Anniversary Update適用済みだったので、新機能の「Bash on Ubuntu on Windows」を使う手も考えたのですが、慣れ親しんだCentOSでやりたいのもあります。

いや待てよ。そういえば「Docker for Windows」を先月末にリリースされたんだっけ!?
というわけで、「Docker for Windows(Ver1.12)」をPCにインストールすることになりました。(長い前置きでしたね。。。)

Docker for Windowsインストール

まずは事前作業として、Hyper-Vを有効化せよとのこと。正直、Hyper-Vを試したことはありません。
「プログラムと機能」→「Windowsの機能の有効化または無効化」を確認してみると、すでにHyper-Vは有効化になっていました。Windows10ではデフォルトで有効になっているのでしょうか?

次にDockerのインストーラーをダウンロードしました。下記のリンクから取得できます。

https://www.docker.com/products/docker#/windows

「InstallDocker.msi」をクリックで簡単にインストールできました!

「Docker for Windows」のアイコンをクリックしてみます。

f:id:no14141:20160827084105j:plain

すると、タスクトレイにくじらが泳ぎ始めます。

f:id:no14141:20160827084313j:plain

これで準備完了!?

コマンドプロンプトPowerShellで、確認してみると・・・

> docker -v
Docker version 1.12.0, build 8eab29e

オッケー、オッケー!!! 無事にdockerの起動に成功しました。

CentOSコンテナ作成

まずは、CentOSの最新版のDockerイメージを取得。

> docker pull centos:latest

> docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
centos               latest              970633036444        3 weeks ago         196.7 MB

次に、docker runでコンテナ作成・実行します。

> docker run -it --name mycentos centos /bin/bash
[root@f84680d43af6 /]# 

これでcentos7のコンテナの中に入ることができました!
試しに、vimを導入してみる。

[root@f84680d43af6 /]# vim
bash: vim: command not found
[root@f84680d43af6 /]# yum -y install vim
:
[root@f84680d43af6 /]# exit

再接続は、docker attachで。初回接続時に導入したvimもきちんと残っています。

> docker attach mycentos
[root@f84680d43af6 /]# yum list installed | grep vim
vim-common.x86_64                      2:7.4.160-1.el7                  @base
vim-enhanced.x86_64                    2:7.4.160-1.el7                  @base
vim-filesystem.x86_64                  2:7.4.160-1.el7                  @base
vim-minimal.x86_64                     2:7.4.160-1.el7                  @CentOS

これでDocker上のCentOSコンテナでLinuxの勉強をする準備が整いました。
このやり方以外で便利なものもたくさんあるのでしょうが、Dockerにしろ、Linuxにしろ、ひとつずつやりながらマスターしていく必要がありそうです。

おまけ:Docker起動でのエラー

別のスペックの低いPCでもDockerを入れてみましたが、そのときに発生したエラーをまとめておきます。

Not Enough memory to start Docker

f:id:no14141:20160825122118j:plain

PCのメモリが4GBと少ないから発生したエラーでしょうか。「Settings」の「Advanced」でメモリサイズを1536MB(デフォルトは2048MB)へ変更したら、無事に起動できました。ベストなサイズがどの程度かはわかりません。。。

f:id:no14141:20160825122334j:plain

The VM couldn't get on IP address

f:id:no14141:20160826080736j:plain

VMIPアドレスが設定できないというエラー。よくわかりません。dockerをexitして、リトライしたら、普通に起動できました。原因は正直、わかりません。