20190411のdockerに関する記事は14件です。

初心者teamlabのオンラインスキルアップ課題step1をやってみた(コード初心者向け記事)

dockerって何とか、DBとの連携でコード上で具体的にどうするのとか、初心者的にはイメージがつかないほど良くわからないので、teamlabさんのオンラインスキルアップ課題をやってみた。

こちらから課題にアクセスします。

STEP1の内容は下記のようになっています。

  1. インターネットのしくみ
  2. WEBサーバについて
  3. HTMLとCSSについて 4-a. 【Windows】Dockerと開発環境の作り方 4-b. 【Mac】Dockerと開発環境の作り方
  4. PHPを書いてみる
  5. PHPでGET/POSTをやってみる
  6. データベースについて
  7. SQLを書いてみる
  8. PHPでデータベースを操作してみる
  9. 10. GitとGitHubについて

4-b:【Mac】Dockerと開発環境の作り方

1,2,3はわかった気になってとりあえず進めて、まずは4のDockerのセットアップから入ります。

docker-compose -v

dockerがちゃんとインストールされているか上記のコマンドで確かめます。

docker-compose up -d

teamlabさんのサイトからダウンロードしたフォルダに遷移して、上のコマンドを叩くとコンテナを作成して、起動します。

※Dockerのコマンドに関してはこちら

スクリーンショット 2019-04-09 15.36.00.png
なんかいろいろエラーは起きたのですが、無事?doneになっています。
「skillup-php-step1-master」をダウンロードして解凍したフォルダに移動したいけど移動できない!っていう場合にですが、フォルダさえ探せれば、フォルダごとターミナルに落としてあげることでpathが表示されるので、あとは先頭にcdつけるだけでフォルダに移動できるはずです。

1-6:PHPでGET/POSTをやってみる

1-5は普通にコピペでコード貼って、Dockerで立ち上げたlocalhostにアクセスするだけなので、はしおります。

まず、エディターがない人は適当なエディターをインストールしてください。
(ぼくはsublimeをつかっています。)

とりあえず、課題のpostだけやってみました

index.html1

<!DOCTYPE html>
<html>
  <head>
<meta charset="UTF-8" />
<title>POSTのサンプル</title>
</head>
<body>
<p>コメントしてください。</p>
    <form method="POST" action="index.php">
      <input name="comment" />
      <input type="submit" value="送信" />
     </form>
 </body>
</html> 

php1

<?php
  //commentがPOSTされているなら
  if(isset($_POST["comment"])){
    //エスケープしてから表示
    $comment = htmlspecialchars($_POST["comment"]);
    print("あなたのコメントは「 ${comment} 」です。");
  } else {
?>

最後にこの課題があります。

[課題]送信する内容を変更してみよう
掲示板に必要な情報としては少なくとも名前と本文が必要です。名前も送信し、受け取るように変更してみましょう。余力のある人はタイトル・文字色・メールアドレスなども追加してみましょう。

なんかとりえあえず複数のデータを送ってみてほしいとのことだったので、僕は次のようにしました。

index.html2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>

<style> 
.cell{ 
  padding: 1px 15px; 
  margin: 1px 30px; 
  line-height: 0.1; 
} </style> 

    <h1>「同窓会の参加有無」</h1>
    <div class=cell> 
      <form method="POST" action="index.php">
        <ol>
         <li><p>氏名:<input name="name"  /></p></li>
          <li><p>メール:<input name="mail" /></p></li>
          <li><p>参加可否:<input name="comment" /></p></li>
      <input type="submit" value="送信" style=         "width:150px;,height:100px;"/>
    </div> 
    </form>
  </body>
</html>

php 2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>
    <?php
      //commentがPOSTされているなら
          if(isset($_POST["mail"],$_POST["name"],$_POST["comment"])){
        //エスケープしてから表示
        $mails = htmlspecialchars($_POST["mail"]);
        $names = htmlspecialchars($_POST["name"]);
        $comments = htmlspecialchars($_POST["comment"]);
        print("あなたの入力した情報は以下になります。<br/>");
        print("名前:${mails}<br/>");
        print("メール:${names}<br/>");
        print("参加有無:${comments}<br/>");
      }else{
        print("情報の入力が正しくありません。");
        }
    ?>
  </body>
</html>

震えるくらいクソコードであることはなんとなくですが、自覚してます....

STEP1-9.PHPでデータベースを操作してみる

これやってみたのですが、すごい勉強になりました。
phpでデータベースをこうやっていじるんだ!みたいなのがなんとなくですが理解できます。

こちらも特に説明を読んでいけば問題なく進めました。

唯一初心者目線でひっかかりそうなのが、データベースにアクセスするときに自分でつけたデータベースの名前じゃないと、エラーになるので注意が必要。
僕の場合はdbname=「TEST」ではなくて「text」でしたので最初はエラってました。

実行結果1

スクリーンショット 2019-04-09 19.22.48.png

先程はDBのname,textを全て表示したが今度はJohnにしぼったものだけを表示させます。

実行コード

<?php
$dsn = 'pgsql:dbname=test;host=pgsql;port=5432';
$user = 'postgres';
$pass = 'example';

try {
  // DBに接続する
  $dbh = new PDO($dsn, $user, $pass);
//$query_result = $dbh->query('SELECT * FROM test_comments');
  $sth_select = $dbh->prepare('SELECT * FROM test_comments WHERE name = ?');
// prepareメソッド(INSERT)最下部でinsertするname/textを定義
  $sth = $dbh->prepare('INSERT INTO test_comments (name, text) VALUES (?, ?)');
 // DBを切断する
  $dbh = null;
} catch (PDOException $e) {
    // 接続にエラーが発生した場合ここに入る
    print "DB ERROR: " . $e->getMessage() . "<br/>";
    die();
}
?>
<?php
//これでdbのnameとtextに新たに文字列が加わる
  $name = "John";
  $text = "Power to the People";
  $sth->execute(array($name, $text));
?>
<?php
  $name = "John";
  $sth_select->execute(array($name));
  //実行したクエリから実行結果を取得している
  $prepare_result = $sth_select->fetchAll();
  foreach($prepare_result as $row) {
    print $row["name"] . ": " . $row["text"] . "<br/>";
  }
  $sth_select->execute(array($name));
?>

実行結果2

スクリーンショット 2019-04-09 20.34.46.png

とりあえず、無事表示されました。

次は気が向いたらSTEP2をやります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がteamlabのオンラインスキルアップ課題step1をやってみた(コード初心者向け記事)

dockerって何とか、DBとの連携でコード上で具体的にどうするのとか、初心者的にはイメージがつかないほど良くわからないので、teamlabさんのオンラインスキルアップ課題をやってみた。

こちらから課題にアクセスします。

STEP1の内容は下記のようになっています。

  1. インターネットのしくみ
  2. WEBサーバについて
  3. HTMLとCSSについて 4-a. 【Windows】Dockerと開発環境の作り方 4-b. 【Mac】Dockerと開発環境の作り方
  4. PHPを書いてみる
  5. PHPでGET/POSTをやってみる
  6. データベースについて
  7. SQLを書いてみる
  8. PHPでデータベースを操作してみる
  9. 10. GitとGitHubについて

4-b:【Mac】Dockerと開発環境の作り方

1,2,3はわかった気になってとりあえず進めて、まずは4のDockerのセットアップから入ります。

docker-compose -v

dockerがちゃんとインストールされているか上記のコマンドで確かめます。

docker-compose up -d

teamlabさんのサイトからダウンロードしたフォルダに遷移して、上のコマンドを叩くとコンテナを作成して、起動します。

※Dockerのコマンドに関してはこちら

スクリーンショット 2019-04-09 15.36.00.png
なんかいろいろエラーは起きたのですが、無事?doneになっています。
「skillup-php-step1-master」をダウンロードして解凍したフォルダに移動したいけど移動できない!っていう場合にですが、フォルダさえ探せれば、フォルダごとターミナルに落としてあげることでpathが表示されるので、あとは先頭にcdつけるだけでフォルダに移動できるはずです。

1-6:PHPでGET/POSTをやってみる

1-5は普通にコピペでコード貼って、Dockerで立ち上げたlocalhostにアクセスするだけなので、はしおります。

まず、エディターがない人は適当なエディターをインストールしてください。
(ぼくはsublimeをつかっています。)

とりあえず、課題のpostだけやってみました

index.html1

<!DOCTYPE html>
<html>
  <head>
<meta charset="UTF-8" />
<title>POSTのサンプル</title>
</head>
<body>
<p>コメントしてください。</p>
    <form method="POST" action="index.php">
      <input name="comment" />
      <input type="submit" value="送信" />
     </form>
 </body>
</html> 

php1

<?php
  //commentがPOSTされているなら
  if(isset($_POST["comment"])){
    //エスケープしてから表示
    $comment = htmlspecialchars($_POST["comment"]);
    print("あなたのコメントは「 ${comment} 」です。");
  } else {
?>

最後にこの課題があります。

[課題]送信する内容を変更してみよう
掲示板に必要な情報としては少なくとも名前と本文が必要です。名前も送信し、受け取るように変更してみましょう。余力のある人はタイトル・文字色・メールアドレスなども追加してみましょう。

なんかとりえあえず複数のデータを送ってみてほしいとのことだったので、僕は次のようにしました。

index.html2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>

<style> 
.cell{ 
  padding: 1px 15px; 
  margin: 1px 30px; 
  line-height: 0.1; 
} </style> 

    <h1>「同窓会の参加有無」</h1>
    <div class=cell> 
      <form method="POST" action="index.php">
        <ol>
         <li><p>氏名:<input name="name"  /></p></li>
          <li><p>メール:<input name="mail" /></p></li>
          <li><p>参加可否:<input name="comment" /></p></li>
      <input type="submit" value="送信" style=         "width:150px;,height:100px;"/>
    </div> 
    </form>
  </body>
</html>

php 2

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>POSTのサンプル</title>
  </head>
  <body>
    <?php
      //commentがPOSTされているなら
          if(isset($_POST["mail"],$_POST["name"],$_POST["comment"])){
        //エスケープしてから表示
        $mails = htmlspecialchars($_POST["mail"]);
        $names = htmlspecialchars($_POST["name"]);
        $comments = htmlspecialchars($_POST["comment"]);
        print("あなたの入力した情報は以下になります。<br/>");
        print("名前:${mails}<br/>");
        print("メール:${names}<br/>");
        print("参加有無:${comments}<br/>");
      }else{
        print("情報の入力が正しくありません。");
        }
    ?>
  </body>
</html>

震えるくらいクソコードであることはなんとなくですが、自覚してます....

STEP1-9.PHPでデータベースを操作してみる

これやってみたのですが、すごい勉強になりました。
phpでデータベースをこうやっていじるんだ!みたいなのがなんとなくですが理解できます。

こちらも特に説明を読んでいけば問題なく進めました。

唯一初心者目線でひっかかりそうなのが、データベースにアクセスするときに自分でつけたデータベースの名前じゃないと、エラーになるので注意が必要。
僕の場合はdbname=「TEST」ではなくて「text」でしたので最初はエラってました。

実行結果1

スクリーンショット 2019-04-09 19.22.48.png

先程はDBのname,textを全て表示したが今度はJohnにしぼったものだけを表示させます。

実行コード

<?php
$dsn = 'pgsql:dbname=test;host=pgsql;port=5432';
$user = 'postgres';
$pass = 'example';

try {
  // DBに接続する
  $dbh = new PDO($dsn, $user, $pass);
//$query_result = $dbh->query('SELECT * FROM test_comments');
  $sth_select = $dbh->prepare('SELECT * FROM test_comments WHERE name = ?');
// prepareメソッド(INSERT)最下部でinsertするname/textを定義
  $sth = $dbh->prepare('INSERT INTO test_comments (name, text) VALUES (?, ?)');
 // DBを切断する
  $dbh = null;
} catch (PDOException $e) {
    // 接続にエラーが発生した場合ここに入る
    print "DB ERROR: " . $e->getMessage() . "<br/>";
    die();
}
?>
<?php
//これでdbのnameとtextに新たに文字列が加わる
  $name = "John";
  $text = "Power to the People";
  $sth->execute(array($name, $text));
?>
<?php
  $name = "John";
  $sth_select->execute(array($name));
  //実行したクエリから実行結果を取得している
  $prepare_result = $sth_select->fetchAll();
  foreach($prepare_result as $row) {
    print $row["name"] . ": " . $row["text"] . "<br/>";
  }
  $sth_select->execute(array($name));
?>

実行結果2

スクリーンショット 2019-04-09 20.34.46.png

とりあえず、無事表示されました。

次は気が向いたらSTEP2をやります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでFailed to fetch debian

突然Dockerが起動しなくなった。

Macのストレージが一杯になったので、Dockerimage系ファイル全部消しました。
(50GBくらい減った。)
いざ再起動しようとすると、、
$ ./docker/bin/compose build
スクリーンショット 0031-03-29 17.47.21.png

W: Failed to fetch http://deb.debian.org/debian/dists/jessie-updates/main/binary-amd64/Packages  404  Not Found.

debian触ってないし、先月まで動いていたし、、

先人の知恵

皆困ってた。
https://stackoverflow.com/questions/55361762/apt-get-update-fails-with-404-in-a-previously-working-build

Dockerfileのapt-get updateする前に

RUN sed -i '/jessie-updates/d' /etc/apt/sources.list 

入れたらできました!:dancer:

debianのライブラリの置き場所が変わったみたいです。

(3月後半から起き始めたみたいで、stackoverflowに感謝です)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Prisma

Prisma.ioとは?

prisma.png

  • SQLサーバにGraphQLを生やします。
  • 今の所、MySQL、PostgreSQL、MongoDBに対応。
  • PrismaサーバはDockerコンテナとして起動。
  • GraphQLなので、クライアントはHTTPが使えればPrismaサーバを操作できる。
  • Prismaサーバの操作をしやすくするPrismaクライアント(GO, TypeScript, JavaScript)を自動生成できる。
  • Prisma Adminでブラウザからデータベースの照会、更新などができる。

雑なまとめなので、公式サイトを見てもらったほうがいいと思います。

また、以下の記事がすごく参考になりました。ありがとうございます。

prisma - 最速 GraphQL Server実装 - Qiita
Prisma.ioでGraphQL APIサーバーを楽して作る - Qiita

構成

PrismaサーバはSQLサーバのCRUD全てが出来てしまうので、そのまま公開するのは危険。
なので、アプリケーション/APIサーバの層を追加します。

この層はPrismaクライアントを使って自前で作るので、APIはGraphQLでなくてもOK。
(REST,gRPCとか)

SUH6AqW.png

  • Database
    MySQL。Dockerで構築。prisma initで自動的に作ってくれる。

  • Data Access Layer(Prisma)
    Dockerで構築。prisma initで自動的に作ってくれる。

  • Application / API Service
    今回はGoで作成。 GraphQLサーバのフレームワークはgqlgenを使用。
    最初はホストで直接起動。後からDocker化。

  • Client(ブラウザ)
    gqlgenGraphQL Playgroundを追加できるので、そこから操作。
    自分はGraphiQLも使ってます。

基本的に公式のチュートリアルに沿って進めますが、所々アレンジ入れてます。

完成後のソースはこちらです。

環境一覧

試した時の環境です。
環境やバージョンが違っても動くとは思います。

  • macOS Mojave
  • Docker version 18.09.2, build 6247962
  • docker-compose version 1.21.2, build a133471
  • Node.js v8.15.1 ※prismaインストールのため

公式ではbrewも書いてありますが、途中で以下のエラーに遭遇したので、
npmで入れ直しました。

brew tap prisma/prisma
brew install prisma

Error: Cannot find module 'generate'

Step1 Set up Prisma

Prisma インストール

$ npm install -g prisma
$ prisma -v
prisma/1.30.0 (darwin-x64) node-v8.15.1

Prisma init

構築はGOHOMEのディレクトリ下で構築します。(例: ~/go/src/prisma-hello-world)

まず、prismaコマンドで土台を作ります。
途中質問がくるので、以下を選択。

  • Create new database
  • MySQL
  • Go
$ cd ~/go/src
$ prisma init prisma-hello-world

? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma Go Client

Created 3 new files:                                                                          

  prisma.yml           Prisma service definition
  datamodel.prisma    GraphQL SDL-based datamodel (foundation for database)
  docker-compose.yml   Docker configuration file

Next steps:

  1. Open folder: cd prisma-hello-world
  2. Start your Prisma server: docker-compose up -d
  3. Deploy your Prisma service: prisma deploy
  4. Read more about Prisma server:
     http://bit.ly/prisma-server-overview

実行が終わるとファイルがいくつか作成されています。

まず、DBサーバとPrismaサーバを起動するdocker-composeファイル。

docker-compose.yml
version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.30
    restart: always
    ports:
    - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: mysql
            host: mysql
            user: root
            password: prisma
            rawAccess: true
            port: 3306
            migrations: true
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
volumes:
  mysql:

データモデル定義のパスや、Prismaクライアントの出力先とかの設定ファイル。

prisma.yml
endpoint: http://localhost:4466
datamodel: datamodel.prisma

generate:
  - generator: go-client
    output: ./generated/prisma-client/

データモデル定義。

datamodel.prisma
type User {
  id: ID! @unique
  name: String!
}

起動・デプロイ

まずは初期状態で起動してみます。

$ docker-compose up -d
$ prisma deploy

以下にアクセスすると、PrismaサーバのGraphQL Playgroundが開きます。

http://localhost:4466
prisma_playground.png

以下のアドレスにアクセスすると、データ管理が出来るPrisma Adminが開きます。

http://localhost:4466/_admin
prisma_admin.png

Goクライアントを作成

データが空っぽなので、Goクライアントを実装してデータを追加してみます。

まず、GO MODULESを初期化します。

$ export GO111MODLUE=on
$ go init

Prismaクライアントを使ってデータを登録するソースを作成します。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user
    name := "Alice"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name: name,
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    users, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", users)
}

実行すると、1件データが登録されます。
IDはcuidに基づいて自動的に振られます。

$ go run index.go
Created new user: &{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}
[{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}]

Step2 データモデルの変更

データモデルに項目を追加します。

datamodel.prisma
type User {
  id: ID! @unique
  email: String @unique
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID! @unique
  title: String!
  published: Boolean! @default(value: "false")
  author: User
}

デプロイとPrismaクライアントの更新をします。

$ prisma deploy
$ prisma generate

generated/prisma-client/prisma.goに新しいAPIが追加されましたので、これを使ってデータを登録するソースを作成します。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user with two posts
    name := "Bob"
    email := "bob@prisma.io"
    title1 := "Join us for GraphQL Conf in 2019"
    title2 := "Subscribe to GraphQL Weekly for GraphQL news"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name:  name,
        Email: &email,
        Posts: &prisma.PostCreateManyWithoutAuthorInput{
            Create: []prisma.PostCreateWithoutAuthorInput{
                prisma.PostCreateWithoutAuthorInput{
                    Title: title1,
                },
                prisma.PostCreateWithoutAuthorInput{
                    Title: title2,
                },
            },
        },
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    allUsers, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allUsers)

    allPosts, err := client.Posts(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allPosts)
}

実行すると、新しいユーザBobと記事が2件追加されます。

$ go run index.go
Created new user: &{ID:cjuc1dwo1002207164z8feea9 Email:0xc000093520 Name:Bob}
[{ID:cjuc0tk8f001l07165y3waxtt Email:<nil> Name:Alice} {ID:cjuc1dwo1002207164z8feea9 Email:0xc000146320 Name:Bob}]
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

次に、登録した記事をemailを指定して検索してみます。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    email := "bob@prisma.io"
    postsByUser, err := client.User(prisma.UserWhereUniqueInput{
        Email: &email,
    }).Posts(nil).Exec(ctx)

    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", postsByUser)
}
$ go run index.go 
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

Step3 Build an App

次に、公開用のGraphQLサーバを作っていきます。

まず、gqlgenのパッケージを登録し、初期構築コマンドを入力します。

$ go get github.com/99designs/gqlgen
$ go run github.com/99designs/gqlgen init

実行すると以下のファイルが出来ます。

  • gqlgen.yml
    gqlgenの設定。自動生成コードの出力先とかを設定。

  • schema.graphql
    公開するGrapQLのスキーマ。この定義からコードが自動生成される。

  • generated.go
    gqlgenで自動生成されるコード。
    自動生成するので、一旦削除。

  • models_gen.go
    prisma-clientで作成された構造体を使うので不要。削除。

  • resolver.go
    GraphQLのリゾルバ。自分で作る必要があるが、必要な関数などのテンプレートは自動生成してくれる。
    自動生成するので、一旦削除。

  • server/server.go
    GraphQLサーバ起動のコード

ごちゃごちゃして来たので、フォルダを作って整理します。

  • gqlgen/
    • gqlgen.yml
    • schema.graphql
  • server/server.go

次に、gqlgenの設定をPrismaに合わせて書き換えます。

gqlgen.yml
schema: schema.graphql
exec:
  filename: generated.go
models:
  Post:
    model: prisma-hello-world/generated/prisma-client.Post
  User:
    model: prisma-hello-world/generated/prisma-client.User
resolver:
  filename: resolver.go
  type: Resolver

公開するGraphQLのスキーマを作成します。

schema.graphql
type Query {
  publishedPosts: [Post!]!
  post(postId: ID!): Post
  postsByUser(userId: ID!): [Post!]!
}

type Mutation {
  createUser(name: String!): User
  createDraft(title: String!, userId: ID!): Post
  publish(postId: ID!): Post
}

type User {
  id: ID!
  email: String
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  published: Boolean!
  author: User
}

ファイルが出来たら、gqlgenを実行してソースを自動生成します。

$ cd gqlgen
$ go run github.com/99designs/gqlgen

以下のファイルが出来ました。

  • gqlgen/
    • generated.go
    • resolver.go

自動生成されたGraphQLのリゾルバは枠しかないので、Prismaクライアントを使って実装していきます。

gqlgen/resolver.go
//go:generate go run github.com/99designs/gqlgen
package gqlgen

import (
    "context"
    "prisma-hello-world/generated/prisma-client"
)

type Resolver struct {
    Prisma *prisma.Client
}

func (r *Resolver) Mutation() MutationResolver {
    return &mutationResolver{r}
}
func (r *Resolver) Post() PostResolver {
    return &postResolver{r}
}
func (r *Resolver) Query() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) User() UserResolver {
    return &userResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*prisma.User, error) {
    return r.Prisma.CreateUser(prisma.UserCreateInput{
        Name: name,
    }).Exec(ctx)
}
func (r *mutationResolver) CreateDraft(ctx context.Context, title string, userId string) (*prisma.Post, error) {
    return r.Prisma.CreatePost(prisma.PostCreateInput{
        Title: title,
        Author: &prisma.UserCreateOneWithoutPostsInput{
            Connect: &prisma.UserWhereUniqueInput{ID: &userId},
        },
    }).Exec(ctx)
}
func (r *mutationResolver) Publish(ctx context.Context, postId string) (*prisma.Post, error) {
    published := true
    return r.Prisma.UpdatePost(prisma.PostUpdateParams{
        Where: prisma.PostWhereUniqueInput{ID: &postId},
        Data:  prisma.PostUpdateInput{Published: &published},
    }).Exec(ctx)
}

type postResolver struct{ *Resolver }

func (r *postResolver) Author(ctx context.Context, obj *prisma.Post) (*prisma.User, error) {
    return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &obj.ID}).Author().Exec(ctx)
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) PublishedPosts(ctx context.Context) ([]prisma.Post, error) {
    published := true
    return r.Prisma.Posts(&prisma.PostsParams{
        Where: &prisma.PostWhereInput{Published: &published},
    }).Exec(ctx)
}
func (r *queryResolver) Post(ctx context.Context, postId string) (*prisma.Post, error) {
    return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &postId}).Exec(ctx)
}
func (r *queryResolver) PostsByUser(ctx context.Context, userId string) ([]prisma.Post, error) {
    return r.Prisma.Posts(&prisma.PostsParams{
        Where: &prisma.PostWhereInput{
            Author: &prisma.UserWhereInput{
                ID: &userId,
            }},
    }).Exec(ctx)
}

type userResolver struct{ *Resolver }

func (r *userResolver) Posts(ctx context.Context, obj *prisma.User) ([]prisma.Post, error) {
    return r.Prisma.User(prisma.UserWhereUniqueInput{ID: &obj.ID}).Posts(nil).Exec(ctx)
}

先頭の//go:generate go run github.com/99designs/gqlgenは、go generateコマンドでgqlgenを実行するためのコメントです。
スキーマを修正したら、以下のコマンドでコードを更新できます。

$ go generate gqlgen/resolver.go

次に、アプリ起動部分を作成します。

server/server.go
package main

import (
    "log"
    "net/http"
    "os"
    prisma "prisma-hello-world/generated/prisma-client"
    "prisma-hello-world/gqlgen"

    "github.com/99designs/gqlgen/handler"
)

const defaultPort = "4000"

func main() {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = defaultPort
    }

    client := prisma.New(nil)
    resolver := gqlgen.Resolver{
        Prisma: client,
    }

    http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
    http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
        gqlgen.Config{Resolvers: &resolver})))

    log.Printf("Server is running on http://localhost:%s", port)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal(err)
    }
}

できたら、起動してみます。

$ go run server/server.go

以下にアクセスすると、GraphQL Playgroundが開きます。

http://localhost:4000/
gql-app.png

ためしに、ユーザや記事を追加してみます。

query-ユーザ追加
mutation {
    createUser(name: "otanu") {
    id
    name
  }  
}
結果
{
  "data": {
    "createUser": {
      "id": "cjuc3vysh000d0744f8n94vw4",
      "name": "otanu"
    }
  }
}
query-記事追加
mutation {
    createDraft(title: "テスト", userId: "cjuc3vysh000d0744f8n94vw4") {
    id
    title
    published
    author {
      id
      name
    }
  }
}
結果
{
  "data": {
    "createDraft": {
      "id": "cjuc42km0000j07441ucjddnd",
      "title": "テスト",
      "published": false,
      "author": {
        "id": "cjuc3vysh000d0744f8n94vw4",
        "name": "otanu"
      }
    }
  }
}
query-公開
mutation {
    publish(postId: "cjuc42km0000j07441ucjddnd") {
    id
    title
    published
    author {
      name
    }
  }
}
結果
{
  "data": {
    "publish": {
      "id": "cjuc42km0000j07441ucjddnd",
      "title": "テスト",
      "published": true,
      "author": {
        "name": "otanu"
      }
    }
  }
}
query-公開記事検索
query {
  publishedPosts {
    id
    title
  }
}
結果
{
  "data": {
    "publishedPosts": [
      {
        "id": "cjuc42km0000j07441ucjddnd",
        "title": "テスト"
      }
    ]
  }
}

アプリのDocker化

アプリもDockerComposeでまとめて起動できるように、Docker化していきます。

アプリをDocker化すると、prismaサーバへの接続がlocalhostでは繋がらななくなるので、環境変数ENDPOINTを追加して、エンドポイントを変更できるようにします。

server/server.go
package main

import (
    "log"
    "net/http"
    "os"
    prisma "prisma-hello-world/generated/prisma-client"
    "prisma-hello-world/gqlgen"

    "github.com/99designs/gqlgen/handler"
)

const defaultPort = "4000"

func main() {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = defaultPort
    }

  // 追加
    var opt *prisma.Options
    endpoint := os.Getenv("ENDPOINT")
    if len(endpoint) != 0 {
        opt = &prisma.Options{
            Endpoint: endpoint,
        }
    }

    client := prisma.New(opt)
    resolver := gqlgen.Resolver{
        Prisma: client,
    }

    http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
    http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
        gqlgen.Config{Resolvers: &resolver})))

    log.Printf("Server is running on http://localhost:%s", port)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal(err)
    }
}

次にDockerfileを準備します。
ついでにfreshでホットリロードも追加。

Dockerfile
FROM golang:1.11-alpine AS build_base
RUN apk add bash ca-certificates git gcc g++ libc-dev

WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get github.com/pilu/fresh

COPY . .
EXPOSE 4000
CMD cd server; fresh server.go

DockerComposeにアプリの設定を追加。
これで、まとめて起動できるようになりました。

docker-compose.yml
version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.30
    restart: always
    ports:
    - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: mysql
            host: mysql
            user: root
            password: prisma
            rawAccess: true
            port: 3306
            migrations: true
  mysql:
    image: mysql:5.7
    restart: always
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "4000:4000"
    volumes:
      - .:/app
    depends_on:
      - prisma
    environment:
      ENDPOINT: http://prisma:4466
volumes:
  mysql:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Prisma.io + Go のチュートリアル

Prisma.ioとは?

prisma.png

  • SQLサーバにGraphQLを生やします。
  • 今の所、MySQL、PostgreSQL、MongoDBに対応。
  • PrismaサーバはDockerコンテナとして起動。
  • GraphQLなので、クライアントはHTTPが使えればPrismaサーバを操作できる。
  • Prismaサーバの操作をしやすくするPrismaクライアント(GO, TypeScript, JavaScript)を自動生成できる。
  • Prisma Adminでブラウザからデータベースの照会、更新などができる。

雑なまとめなので、公式サイトを見てもらったほうがいいと思います。

また、以下の記事がすごく参考になりました。ありがとうございます。

prisma - 最速 GraphQL Server実装 - Qiita
Prisma.ioでGraphQL APIサーバーを楽して作る - Qiita

構成

PrismaサーバはSQLサーバのCRUD全てが出来てしまうので、そのまま公開するのは危険。
なので、アプリケーション/APIサーバの層を追加します。

この層はPrismaクライアントを使って自前で作るので、APIはGraphQLでなくてもOK。
(REST,gRPCとか)

SUH6AqW.png

  • Database
    MySQL。Dockerで構築。prisma initで自動的に作ってくれる。

  • Data Access Layer(Prisma)
    Dockerで構築。prisma initで自動的に作ってくれる。

  • Application / API Service
    今回はGoで作成。 GraphQLサーバのフレームワークはgqlgenを使用。
    最初はホストで直接起動。後からDocker化。

  • Client(ブラウザ)
    gqlgenGraphQL Playgroundを追加できるので、そこから操作。
    自分はGraphiQLも使ってます。

基本的に公式のチュートリアルに沿って進めますが、所々アレンジ入れてます。

完成後のソースはこちらです。

環境一覧

試した時の環境です。
環境やバージョンが違っても動くとは思います。

  • macOS Mojave
  • Docker version 18.09.2, build 6247962
  • docker-compose version 1.21.2, build a133471
  • Node.js v8.15.1 ※prismaインストールのため

公式ではbrewも書いてありますが、途中で以下のエラーに遭遇したので、
npmで入れ直しました。

brew tap prisma/prisma
brew install prisma

Error: Cannot find module 'generate'

Step1 Set up Prisma

Prisma インストール

$ npm install -g prisma
$ prisma -v
prisma/1.30.0 (darwin-x64) node-v8.15.1

Prisma init

構築はGOHOMEのディレクトリ下で構築します。(例: ~/go/src/prisma-hello-world)

まず、prismaコマンドで土台を作ります。
途中質問がくるので、以下を選択。

  • Create new database
  • MySQL
  • Go
$ cd ~/go/src
$ prisma init prisma-hello-world

? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma Go Client

Created 3 new files:                                                                          

  prisma.yml           Prisma service definition
  datamodel.prisma    GraphQL SDL-based datamodel (foundation for database)
  docker-compose.yml   Docker configuration file

Next steps:

  1. Open folder: cd prisma-hello-world
  2. Start your Prisma server: docker-compose up -d
  3. Deploy your Prisma service: prisma deploy
  4. Read more about Prisma server:
     http://bit.ly/prisma-server-overview

実行が終わるとファイルがいくつか作成されています。

まず、DBサーバとPrismaサーバを起動するdocker-composeファイル。

docker-compose.yml
version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.30
    restart: always
    ports:
    - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: mysql
            host: mysql
            user: root
            password: prisma
            rawAccess: true
            port: 3306
            migrations: true
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
volumes:
  mysql:

データモデル定義のパスや、Prismaクライアントの出力先とかの設定ファイル。

prisma.yml
endpoint: http://localhost:4466
datamodel: datamodel.prisma

generate:
  - generator: go-client
    output: ./generated/prisma-client/

データモデル定義。

datamodel.prisma
type User {
  id: ID! @unique
  name: String!
}

起動・デプロイ

まずは初期状態で起動してみます。

$ docker-compose up -d
$ prisma deploy

以下にアクセスすると、PrismaサーバのGraphQL Playgroundが開きます。

http://localhost:4466
prisma_playground.png

以下のアドレスにアクセスすると、データ管理が出来るPrisma Adminが開きます。

http://localhost:4466/_admin
prisma_admin.png

Goクライアントを作成

データが空っぽなので、Goクライアントを実装してデータを追加してみます。

まず、GO MODULESを初期化します。

$ export GO111MODLUE=on
$ go init

Prismaクライアントを使ってデータを登録するソースを作成します。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user
    name := "Alice"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name: name,
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    users, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", users)
}

実行すると、1件データが登録されます。
IDはcuidに基づいて自動的に振られます。

$ go run index.go
Created new user: &{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}
[{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}]

Step2 データモデルの変更

データモデルに項目を追加します。

datamodel.prisma
type User {
  id: ID! @unique
  email: String @unique
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID! @unique
  title: String!
  published: Boolean! @default(value: "false")
  author: User
}

デプロイとPrismaクライアントの更新をします。

$ prisma deploy
$ prisma generate

generated/prisma-client/prisma.goに新しいAPIが追加されましたので、これを使ってデータを登録するソースを作成します。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user with two posts
    name := "Bob"
    email := "bob@prisma.io"
    title1 := "Join us for GraphQL Conf in 2019"
    title2 := "Subscribe to GraphQL Weekly for GraphQL news"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name:  name,
        Email: &email,
        Posts: &prisma.PostCreateManyWithoutAuthorInput{
            Create: []prisma.PostCreateWithoutAuthorInput{
                prisma.PostCreateWithoutAuthorInput{
                    Title: title1,
                },
                prisma.PostCreateWithoutAuthorInput{
                    Title: title2,
                },
            },
        },
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    allUsers, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allUsers)

    allPosts, err := client.Posts(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allPosts)
}

実行すると、新しいユーザBobと記事が2件追加されます。

$ go run index.go
Created new user: &{ID:cjuc1dwo1002207164z8feea9 Email:0xc000093520 Name:Bob}
[{ID:cjuc0tk8f001l07165y3waxtt Email:<nil> Name:Alice} {ID:cjuc1dwo1002207164z8feea9 Email:0xc000146320 Name:Bob}]
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

次に、登録した記事をemailを指定して検索してみます。

index.go
package main

import (
    "context"
    "fmt"
    prisma "prisma-hello-world/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    email := "bob@prisma.io"
    postsByUser, err := client.User(prisma.UserWhereUniqueInput{
        Email: &email,
    }).Posts(nil).Exec(ctx)

    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", postsByUser)
}
$ go run index.go 
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

Step3 Build an App

次に、公開用のGraphQLサーバを作っていきます。

まず、gqlgenのパッケージを登録し、初期構築コマンドを入力します。

$ go get github.com/99designs/gqlgen
$ go run github.com/99designs/gqlgen init

実行すると以下のファイルが出来ます。

  • gqlgen.yml
    gqlgenの設定。自動生成コードの出力先とかを設定。

  • schema.graphql
    公開するGrapQLのスキーマ。この定義からコードが自動生成される。

  • generated.go
    gqlgenで自動生成されるコード。
    自動生成するので、一旦削除。

  • models_gen.go
    prisma-clientで作成された構造体を使うので不要。削除。

  • resolver.go
    GraphQLのリゾルバ。自分で作る必要があるが、必要な関数などのテンプレートは自動生成してくれる。
    自動生成するので、一旦削除。

  • server/server.go
    GraphQLサーバ起動のコード

ごちゃごちゃして来たので、フォルダを作って整理します。

  • gqlgen/
    • gqlgen.yml
    • schema.graphql
  • server/server.go

次に、gqlgenの設定をPrismaに合わせて書き換えます。

gqlgen.yml
schema: schema.graphql
exec:
  filename: generated.go
models:
  Post:
    model: prisma-hello-world/generated/prisma-client.Post
  User:
    model: prisma-hello-world/generated/prisma-client.User
resolver:
  filename: resolver.go
  type: Resolver

公開するGraphQLのスキーマを作成します。

schema.graphql
type Query {
  publishedPosts: [Post!]!
  post(postId: ID!): Post
  postsByUser(userId: ID!): [Post!]!
}

type Mutation {
  createUser(name: String!): User
  createDraft(title: String!, userId: ID!): Post
  publish(postId: ID!): Post
}

type User {
  id: ID!
  email: String
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  published: Boolean!
  author: User
}

ファイルが出来たら、gqlgenを実行してソースを自動生成します。

$ cd gqlgen
$ go run github.com/99designs/gqlgen

以下のファイルが出来ました。

  • gqlgen/
    • generated.go
    • resolver.go

自動生成されたGraphQLのリゾルバは枠しかないので、Prismaクライアントを使って実装していきます。

gqlgen/resolver.go
//go:generate go run github.com/99designs/gqlgen
package gqlgen

import (
    "context"
    "prisma-hello-world/generated/prisma-client"
)

type Resolver struct {
    Prisma *prisma.Client
}

func (r *Resolver) Mutation() MutationResolver {
    return &mutationResolver{r}
}
func (r *Resolver) Post() PostResolver {
    return &postResolver{r}
}
func (r *Resolver) Query() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) User() UserResolver {
    return &userResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*prisma.User, error) {
    return r.Prisma.CreateUser(prisma.UserCreateInput{
        Name: name,
    }).Exec(ctx)
}
func (r *mutationResolver) CreateDraft(ctx context.Context, title string, userId string) (*prisma.Post, error) {
    return r.Prisma.CreatePost(prisma.PostCreateInput{
        Title: title,
        Author: &prisma.UserCreateOneWithoutPostsInput{
            Connect: &prisma.UserWhereUniqueInput{ID: &userId},
        },
    }).Exec(ctx)
}
func (r *mutationResolver) Publish(ctx context.Context, postId string) (*prisma.Post, error) {
    published := true
    return r.Prisma.UpdatePost(prisma.PostUpdateParams{
        Where: prisma.PostWhereUniqueInput{ID: &postId},
        Data:  prisma.PostUpdateInput{Published: &published},
    }).Exec(ctx)
}

type postResolver struct{ *Resolver }

func (r *postResolver) Author(ctx context.Context, obj *prisma.Post) (*prisma.User, error) {
    return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &obj.ID}).Author().Exec(ctx)
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) PublishedPosts(ctx context.Context) ([]prisma.Post, error) {
    published := true
    return r.Prisma.Posts(&prisma.PostsParams{
        Where: &prisma.PostWhereInput{Published: &published},
    }).Exec(ctx)
}
func (r *queryResolver) Post(ctx context.Context, postId string) (*prisma.Post, error) {
    return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &postId}).Exec(ctx)
}
func (r *queryResolver) PostsByUser(ctx context.Context, userId string) ([]prisma.Post, error) {
    return r.Prisma.Posts(&prisma.PostsParams{
        Where: &prisma.PostWhereInput{
            Author: &prisma.UserWhereInput{
                ID: &userId,
            }},
    }).Exec(ctx)
}

type userResolver struct{ *Resolver }

func (r *userResolver) Posts(ctx context.Context, obj *prisma.User) ([]prisma.Post, error) {
    return r.Prisma.User(prisma.UserWhereUniqueInput{ID: &obj.ID}).Posts(nil).Exec(ctx)
}

先頭の//go:generate go run github.com/99designs/gqlgenは、go generateコマンドでgqlgenを実行するためのコメントです。
スキーマを修正したら、以下のコマンドでコードを更新できます。

$ go generate gqlgen/resolver.go

次に、アプリ起動部分を作成します。

server/server.go
package main

import (
    "log"
    "net/http"
    "os"
    prisma "prisma-hello-world/generated/prisma-client"
    "prisma-hello-world/gqlgen"

    "github.com/99designs/gqlgen/handler"
)

const defaultPort = "4000"

func main() {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = defaultPort
    }

    client := prisma.New(nil)
    resolver := gqlgen.Resolver{
        Prisma: client,
    }

    http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
    http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
        gqlgen.Config{Resolvers: &resolver})))

    log.Printf("Server is running on http://localhost:%s", port)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal(err)
    }
}

できたら、起動してみます。

$ go run server/server.go

以下にアクセスすると、GraphQL Playgroundが開きます。

http://localhost:4000/
gql-app.png

ためしに、ユーザや記事を追加してみます。

query-ユーザ追加
mutation {
    createUser(name: "otanu") {
    id
    name
  }  
}
結果
{
  "data": {
    "createUser": {
      "id": "cjuc3vysh000d0744f8n94vw4",
      "name": "otanu"
    }
  }
}
query-記事追加
mutation {
    createDraft(title: "テスト", userId: "cjuc3vysh000d0744f8n94vw4") {
    id
    title
    published
    author {
      id
      name
    }
  }
}
結果
{
  "data": {
    "createDraft": {
      "id": "cjuc42km0000j07441ucjddnd",
      "title": "テスト",
      "published": false,
      "author": {
        "id": "cjuc3vysh000d0744f8n94vw4",
        "name": "otanu"
      }
    }
  }
}
query-公開
mutation {
    publish(postId: "cjuc42km0000j07441ucjddnd") {
    id
    title
    published
    author {
      name
    }
  }
}
結果
{
  "data": {
    "publish": {
      "id": "cjuc42km0000j07441ucjddnd",
      "title": "テスト",
      "published": true,
      "author": {
        "name": "otanu"
      }
    }
  }
}
query-公開記事検索
query {
  publishedPosts {
    id
    title
  }
}
結果
{
  "data": {
    "publishedPosts": [
      {
        "id": "cjuc42km0000j07441ucjddnd",
        "title": "テスト"
      }
    ]
  }
}

アプリのDocker化

アプリもDockerComposeでまとめて起動できるように、Docker化していきます。

アプリをDocker化すると、prismaサーバへの接続がlocalhostでは繋がらななくなるので、環境変数ENDPOINTを追加して、エンドポイントを変更できるようにします。

server/server.go
package main

import (
    "log"
    "net/http"
    "os"
    prisma "prisma-hello-world/generated/prisma-client"
    "prisma-hello-world/gqlgen"

    "github.com/99designs/gqlgen/handler"
)

const defaultPort = "4000"

func main() {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = defaultPort
    }

  // 追加
    var opt *prisma.Options
    endpoint := os.Getenv("ENDPOINT")
    if len(endpoint) != 0 {
        opt = &prisma.Options{
            Endpoint: endpoint,
        }
    }

    client := prisma.New(opt)
    resolver := gqlgen.Resolver{
        Prisma: client,
    }

    http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
    http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
        gqlgen.Config{Resolvers: &resolver})))

    log.Printf("Server is running on http://localhost:%s", port)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal(err)
    }
}

次にDockerfileを準備します。
ついでにfreshでホットリロードも追加。

Dockerfile
FROM golang:1.11-alpine AS build_base
RUN apk add bash ca-certificates git gcc g++ libc-dev

WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get github.com/pilu/fresh

COPY . .
EXPOSE 4000
CMD cd server; fresh server.go

DockerComposeにアプリの設定を追加。
これで、まとめて起動できるようになりました。

docker-compose.yml
version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.30
    restart: always
    ports:
    - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: mysql
            host: mysql
            user: root
            password: prisma
            rawAccess: true
            port: 3306
            migrations: true
  mysql:
    image: mysql:5.7
    restart: always
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "4000:4000"
    volumes:
      - .:/app
    depends_on:
      - prisma
    environment:
      ENDPOINT: http://prisma:4466
volumes:
  mysql:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Coreコンソールアプリのコンテナ化について手法図示

はじめに

.Net coreのコンソールアプリをコンテナ化しようといろいろ調べたんですが

自分的にイメージしにくかったので図示してみます。

image.png

①ベースイメージ取得

Docker Hubからベースとなるイメージを取得してくる。
SDKやらRuntimeやらいろいろある。

例えばsdk2.1
docker pull mcr.microsoft.com/dotnet/core/sdk:2.1

②コンテナを起動して中に入る

GitHubからソースコードを取得する想定
docker run -itd --name tmp mcr.microsoft.com/dotnet/core/sdk:2.1 /bin/bash
docker exec -it tmp /bin/bash

共有フォルダからソースコードを取得しようと思ったら--mountで設定必要

③.net coreソースコード取得

例えば.NetCoreのサンプルアプリのソースコードを取得
mkdir -p /src; cd $_
git clone https://github.com/dotnet/core.git
cd core/samples/helloworld

④dotnet publishでdll作成

mkdir /helloworld
dotnet publish -c Release -o /helloworld
dotnet /helloworld/helloworld.dll

で実行可能な状況になる。
配置には

  • フレームワークに依存する展開(Framework-dependent deployment、FDD)
  • 自己完結型の展開(Self-contained deployment、SFD)

があるらしい。
コンソールアプリを外部から呼びやすくする方法として.NET Core CLI を使用して .NET Core グローバル ツールを作成するとかも。

⑤ docker commitでimage化

コンテナから抜け出てから

docker commit tmp helloworld

とするとmcr.microsoft.com/dotnet/core/sdk:2.1をベースとしたhelloworld.dllが実行可能なhelloworldというイメージが新たに出来上がる。
tmpは②で指定したコンテナ名。helloworldはイメージ名。

DockerFile

こんなことを毎回手動ではやってられないのでDockerFileで自動化する。
解説はいきなりDockerFileの記述から始まるので根本的なところがわからなかった。

dockerfile
FROM mcr.microsoft.com/dotnet/core/sdk:2.1
WORKDIR /src
RUN git clone https://github.com/dotnet/core.git
WORKDIR core/samples/helloworld
RUN mkdir /helloworld
RUN dotnet publish -c Release -o /helloworld
ENTRYPOINT ["dotnet", "/helloworld/helloworld.dll"]

上記ファイルがある場所で

docker build -t helloworld .

を実行すればイメージが出来上がる。

docker run helloworld


Hello World!
が出力される。

イメージの配置

  • 実行環境でdocker build
  • 出来上がったイメージを実行環境にエクスポート

とかすれば使えるようになるはず。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS ECRにdockerイメージを登録

ECRとは

Amazon Elastic Container Registry (ECR) は、完全マネージド型の Docker コンテナレジストリです。このレジストリを使うと、開発者は Docker コンテナイメージを簡単に保存、管理、デプロイできます。

ECRにレポジトリを作成

以下のコマンドでECRにレポジトリを作成します。

$ aws ecr create-repository --repository-name sample-aws-batch-repo

ECRにログイン

以下のコマンドを実行すると、ECRにログインします。

$ aws ecr get-login --no-include-email --region ap-northeast-1 --profile ExampleProfile

作成したdocker imageに、以下のようにしてtagを付けます。

$ docker tag sample-aws-batch:latest xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/sample-aws-batch-repo:latest

ECRにpushします。

$ docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/sample-aws-batch-repo:latest

参考:AWSのECR

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose から docker network を少しだけ理解したい

docker-compose での疑問

docker公式っぽいやつの docker-compose の例 : https://docs.docker.com/compose/gettingstarted/

ここでのapp.pyredishostが以下のように指定されています。

cache = redis.Redis(host='redis', port=6379)

なぜ host='redis'で別のコンテナのredisにつなぐことが出来るんだい?

上記の公式サイトには以下のように解説されています。

In this example, redis is the hostname of the redis container on the application’s network. We use the default port for Redis, 6379.

はぇ〜

application’s network ってなに

ここでいう application’s network とは docker container におけるネットワークのことっぽい?

docker-compose up 等で起動されたコンテナ群は、すべて同一のネットワークに所属することになるようです。

参考にさせていただいた記事 : https://qiita.com/roba4coding/items/efd3a38db08eb476d412

つまりどういうことだってばよ

docker inspect コマンドを使うことでふわっと概要がわかります。

inspect するためにまず コンテナのIDを調べます。

$ docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
aa690f2ae5d4        example_docker_compose_web   "python app-server/s…"   2 minutes ago       Up 2 minutes        0.0.0.0:5000->5000/tcp   example_docker_compose_web_1
4f39c7c9cc47        redis:alpine                 "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        6379/tcp                 example_docker_compose_redis_1

inspect します。

$ docker inspect 4f39c7c9cc47
...
            "Networks": {
                "example_docker_compose_default": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "4f39c7c9cc47",
                        "redis"
                    ],
                    ...
                }
            }
...

コンテナのID : 4f39c7c9cc47(example_docker_compose_redis_1) が redis というエイリアスで登録されているのですね。

このエイリアスを使うことが出来るのが、同一 docker network に所属するものだけだということですね。

上記でいうと コンテナ aa690f2ae5d4(example_docker_compose_web_1) が エイリアスを使うことが出来るということですね。

どこで redis というエイリアスの名前が決まったかということですが、docker-compose.ymlに書いてある servicesの直下の定義の名のことみたいですね。

version: '3'
services:
  # ↓これ
  web:
    build: .
    ports:
     - "5000:5000"
  # ↓これ
  redis:
    image: "redis:alpine"

docker network ってなに

docker network ls で現在存在する docker network の一覧を見ることができます。

$ docker network ls
NETWORK ID          NAME                               DRIVER              SCOPE
ac27f0096628        bridge                             bridge              local
e72136cdac25        example_docker_compose_default     bridge              local
d2b0552308a8        host                               host                local

ここで 最初の docker-composeexample で作られたであろう example_docker_compose_defaultdocker networkの詳細を見てみましょう。

コマンドは docker network inspect です。

$ docker network inspect example_docker_compose_default
[
    {
        "Name": "example_docker_compose_default",
        "Id": "e72136cdac25de9dbc9875467aacd59e916ba8b7122cbede91f81387a2a18715",
...
        "Containers": {
            "4f39c7c9cc478dc30aca3bd21c6dc1eda2bb189c0b5264a325f98b38ad8cd7c6": {
                "Name": "example_docker_compose_redis_1",
                ...
            },
            "aa690f2ae5d483c0c2cf213d9929a3d49025859464abe095f861c05bbb66ee32": {
                "Name": "example_docker_compose_web_1",
                ...
            }
        },
...
]

Containers に 先ほど docker-compose で作られたコンテナ達がありますね。コンテナIDも一致しているようです。

参考にさせていただいた記事 : https://qiita.com/TsutomuNakamura/items/ed046ee21caca4a2ffd9

docker-compose を使わずにやってみよう

今回は別の例として mysql が起動しているコンテナ に対して python が起動したコンテナからアクセスしたいと思います。

docker networkを知らなかった時

まずmysqlのコンテナをたてます。

$ docker run -e MYSQL_ROOT_PASSWORD=root -d -p 3307:3306 --name test-mysql mysql:5.6
4f7475ea1ac4db5d4aad630cbe585faf2dca39db61d7e405e14e9554414bd486

python が起動しているコンテナから mysqlのコンテナにアクセスするためには、そのコンテナのアドレスを知る必要があります。

$ docker exec test-mysql cat /etc/hosts
127.0.0.1   localhost
.. 
172.17.0.2  4f7475ea1ac4

こうすることでようやく接続することができました。(もっといいやり方あったら教えてください..)

>>> import mysql.connector
>>> conn = mysql.connector.connect(host="172.17.0.2",user="root",password="root",port=3306,database="mysql")
>>> conn.is_connected()
True

docker networkを使った場合

まず、コンテナが所属するための docker network を作ります。

docker networkに関して参考にさせていただいたブログ : https://www.sambaiz.net/article/7/

$ docker network create -d bridge test-network
25d355cf117def0e6ce801341e1b535320cd688435a436ce063dc49bbb3c9c0e
$ docker network ls
NETWORK ID          NAME                               DRIVER              SCOPE
ac27f0096628        bridge                             bridge              local
e72136cdac25        example_docker_compose_default     bridge              local
d2b0552308a8        host                               host                local
07263a6d0de1        test-network                       bridge              local

コンテナが作成される時に、作ったネットワークに所属してもらうようにするには、--net オプションを使うようです。

$ docker run -e MYSQL_ROOT_PASSWORD=root -d -p 3307:3306 --name test-mysql --net=test-network --net-alias mysql  mysql:5.6
0f901dbda634c3d05cce10d2bf91fb0ef82d39fbd1b1046f284db522bc8387f8

こうすることで、docker-compose で起動したコンテナと同様にエイリアスが登録されます。

$ docker inspect 0f901dbda634
...
            "Networks": {
                "test-network": {
                    ...
                    "Aliases": [
                        "mysql",
                        "0f901dbda634"
                    ]
            ...
            }
...

そして python が起動するコンテナも同一docker network上にたててあげれば、

$ docker run -it --net=test-network test-python
>>> import mysql.connector
>>> conn = mysql.connector.connect(host='mysql',user='root',password='root',port=3306,database='mysql')
>>> conn.is_connected()
True

無事、コンテナを名前解決できるようになり、host='mysql' で接続できているのがわかると思います。

--link の場合

$ docker run -e MYSQL_ROOT_PASSWORD=root -d -p 3307:3306 --name test-mysql --net=test-network  mysql:5.6
2d96784fb9ae77cec276558d3439e3b455782bb4bac4291b76b65a27d5c27480
$ docker run -it --net=test-network --link test-mysql:mysql test-python
Python 3.7.3 (default, Mar 27 2019, 23:48:15)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import mysql.connector
>>> conn = mysql.connector.connect(host='mysql',user='root',password='root',port=3306,database='mysql')
>>> conn.is_connected()
True

同様に繋ぐことが出来ました。

link の場合 AliasesではなくLinksに名前解決するための情報が書かれていました。

...
            "Networks": {
                "test-network": {
                    ...
                    "Links": [
                        "test-mysql:mysql"
                    ],
                    "Aliases": [
                        "d0d2ed057334"
                    ],
                ...
                }
            }
...

--net-alias--link の使い分けに関してはまだ知識が浅いのでいつか調べたいと思います。

まとめ

docker-compose を使おう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsプロジェクトをCloud Runに乗せてデプロイするぜ!(GCPの新しいサービス)

Google Cloud Next'19で新しい発表がありましたね
AWS一筋の人は浮気したくなるじゃない?^^(クラウド戦争の種を撒こう)
image.png

Cloud Runとは

Google Cloud Next’19で発表された新サービス
https://cloud.withgoogle.com/next/sf

コンテナ単位でサーバーレスの実行環境です。一番の目的としてはこれだと思います。
あらゆる言語で書かれたカスタムアプリケーションのサーバーレスでの運用、そして多様な環境にまたがる互換性の確保を目的としている。
ほほっ!!!!

使うもの

Nuxt.js (SSR必須) Docker (クジラかな?)

gcloud (gcpのcli)

GCPのサービス:
Cloud Run (今回の主役) Container Registry (GCPのプロジェクトのコンテナリポジトリ管理)

1.Nuxt.jsプロジェクト作成

image.png
まずはNuxt.jsのプロジェクト設定ですね。
Choose rendering modeはUniversal以外には好きなの選んで大丈夫です。

npx create-nuxt-app nuxt-cloudrun
> Generating Nuxt.js project in /Users/jakushin.cho/Projects/nuxt-cloudrun
? Project name nuxt-cloudrun
? Project description My dandy Nuxt.js project
? Use a custom server framework express
? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Use a custom UI framework vuetify
? Use a custom test framework none
? Choose rendering mode Universal  <--- SSR 
? Author name jakushin
? Choose a package manager npm

ローカルで起動して、問題はないかをチェックしましょう

npm run dev

Vuetify + Nuxt.jsのデフォルト画面
スクリーンショット 2019-04-11 9.56.00.png

2.Dockerファイルの設定

image.png

プロジェクトディレクトリでdockerfileを作成します、内容はこちら

FROM node:10

WORKDIR /usr/src/app

ENV PORT 8080
ENV HOST 0.0.0.0

COPY package*.json ./

RUN npm install --only=production

# Nuxtプロジェクトのコードをコンテナにコピー
COPY . .

# prodビルト、サーバ起動
RUN npm run build
CMD npm start

ファイル作成後、以下のコマンドでdocker image作成

> docker build ./
..
..
..
(ログ省略)
 ---> 317bd77abc49
Successfully built 317bd77abc49  <--- 作成したイメージID

作成したイメージIDで、docker runでnuxtのアプリを起動チェック(スキップ可)

> docker run -p 8080:8080 317bd77abc49
nuxt-cloudrun@1.0.0 start /usr/src/app
cross-env NODE_ENV=production node server/index.js
01:13:09  READY  Server listening on http://0.0.0.0:8080

起動後、0.0.0.0:8080にアクセスし、先程のvuetify+nuxtの画面が表示されたらOK!

動いているコンテナを消し忘れずですね。

> docker kill be0d7f77e431

3.Container Registry (GCP)にコンテナイメージをpush

image.png
GCPでプロジェクトを作成しのが前提です。Cloud Container Registryを有効にしてください。

まずdockerイメージにtag付けましょう。分かりやすくGCPのContainer Registryに統一しましょう。

                   GCPのコンテナホスト名   プロジェクト名    dockerイメージ名(任意)
> docker tag 317bd77abc49 [us.gcr.io]/[myprojectid]/[nuxt-cloudrun]

push先は同じURLでいいので、GCPのコンテナにpush!!!!

> docker push [us.gcr.io]/[myprojectid]/[nuxt-cloudrun]

成功したらリポジトリに追加されます。
スクリーンショット 2019-04-11 10.35.06.png

4.Cloud Run Beta(GCP)

有効にしてくださいね。
CREATE SERVICEをクリックしてで新規作成しましょう。
スクリーンショット 2019-04-11 10.36.57.png

サービス作成画面でコンテナイメージのURLの右の「選択」ボタンをクリックします。
スクリーンショット 2019-04-11 10.40.08.png

Container Registryに上げたnuxt-cloudrunイメージを選びます。
スクリーンショット 2019-04-11 10.40.26.png

一般のユーザに公開したいならば、認証の未認証の呼び出しを許可を選び、「作成」ボタンをクリックします
スクリーンショット 2019-04-11 10.45.12.png

成功した画面です
ログの方の出力はlocalのnuxt.jsのプロジェクトのログが一致しているの確認できます。
スクリーンショット 2019-04-11 10.51.48.png

やったー!コンテナをサーバーレスにできたぜ!!!

日本語が下手くそな僕がまとめようとしても、まとまれないのまとめ

コンテナをサーバーレス化にして、ますますマイクロサービスの時代の流れを感じましたね!
以上!(無責任)
..
..
もう少し書きます。
cloud functionでサーバーレス、cloud runでコンテンをサーバーレス
インフラのことを気にしたくなければ、GCPは凄くいい選択肢ではないか。

料金はこちら
https://cloud.google.com/run/pricing
試しで作ったサービスの消し忘れを注意してくださいね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気持ちいいRuby/Ruby on Rails開発環境を作ったので解説(Mac/Win両対応)

hayakasa/docker-vagrant-template-rails

以下の要件を満たすRuby/Ruby on Railsの開発・実行環境を環境を作成しました。

  • ホストOSの環境を極力汚さないこと
  • 必要最低限の手順で環境構築できること
  • 実行環境のバージョンを管理する手間を極力省くこと
  • ソースコードを共有ディレクトリでマウントし、編集後に即時反映されること
  • Dockerコンテナ側からファイル操作が可能なこと(bundle install、tmp内のファイル作成など)
  • Mac・Windowsどちらでも可能な限り同じ手順で構築し動作すること

rvm、rbenvなどで実行環境のバージョン管理をすることが手間に感じていたのでDockerを採用することを決めたのですが、公式で配布されているDocker Desktop for Mac/Windowsにはソースコードを即時反映できるようにマウントした場合に実行速度が遅くなるなどの問題があります。
この問題を解決するため、ホストOSとDockerコンテナの間にVagrantで管理するVMを挟み、共有ディレクトリをNFSでマウントすることによって実行速度を犠牲にせずにアプリ開発ができる環境を作りました。

前提アプリケーションのインストールや使い方は下記GitHubリポジトリのREADMEを参照ください。
hayakasa/docker-vagrant-template-rails

こちらでは、少し突っ込んだ解説を行います。

NFSについて

Vagrantfileの以下の設定で共有ディレクトリをNFSマウントしています。

Vagrantfile
config.vm.synced_folder ".", "/vagrant-nfs",type:"nfs" #DocRootをvagrant-nfsという名前でNFSマウント

Macの場合

Macでは標準でNFSサーバーが準備されているので以下のコマンドで有効化します。

YourPC$ sudo touch /etc/exports #nfsd設定を保存するファイルを作成
YourPC$ sudo nfsd enable #nfsdの有効化

Windowsの場合

WindowsではVagrantのプラグインを使うことによってNFSサーバーを立てずにNFSでマウントができるためそちらを使用します。

YourPC$ vagrant plugin install vagrant-winnfsd

vagrant-bindfsプラグインについて

NFSでマウントした共有ディレクトリはホストOSのユーザーがownerになっているので、bindfsでvagrantユーザーがownerになるようにマウントし直します。

Vagrantfile
config.bindfs.bind_folder "/vagrant-nfs", "/vagrant" #vagrant-nfsをvagrantという名前でbindfsマウント

vagrant-hostsupdaterプラグインについて

vagrant upでVMを起動するときにhostsファイルにホスト名の登録を行います。これによってホスト名でアプリケーションにアクセスすることができるようになります。
(hostsを操作するためWindowsではコマンドプロンプト・PowerShellを管理者権限で実行する必要があります)

Vagrantfile
config.vm.hostname = "myapp.localhost"

ポートフォワーディングについて

以下の設定によって一般的に使用されるポートでホストOSからアクセスができるようにしています。

docker-compose.yml
services:
  db:
    ports:
      - "5432:5432" #例:PostgreSQLの場合。Dockerコンテナの5432番をVMの5432番にポートフォワーディング
  app:
    command: bundle exec rails s -p 3000 -b '0.0.0.0' --no-dev-caching #rails s を3000番ポートで実行
    ports:
      - "80:3000" #Dockerコンテナの3000番ポートをVMの80番にポートフォワーディング
Vagrantfile
  config.vm.network "forwarded_port", guest: 80, host: 80 #VMの80番をホストOSの80番にポートフォワーディング
  config.vm.network "forwarded_port", guest: 5432, host: 5432 #VMの5432番をホストOSの5432番にポートフォワーディング

データベースのvolumeについて

以下の設定によってデータベースの内容はVM内で永続化しています。したがってVMのイメージ自体を削除しない限り、Dockerコンテナを破棄してもデータベースの中身は保持されます。

docker-compose.yml
services:
  db:
    volumes:
      - /data/db:/var/lib/postgresql/data  #例:PostgreSQLの場合 Dockerコンテナの/var/lib/postgresql/dataをVMの/data/dbに保存します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ連携の平成世代sftpクライアントだってコンテナになりたい令和元年のtips

俺です

昭和の僕が思い浮かぶ外部連携といえばftpです。全銀tcp/ip最高みたいな話です。
sftpクライアントなコンテナを使い、他のサーバからファイルをgetしてから後続のシェルコマンドやスクリプトで何かしたい。というアレがアレなものをdocker-composeのvolumeを使い、dockerホストにsftpで取得したファイルを保管して動かすtipsです

sftpクライアント用のコンテナにはこちらを使います。

定義

  • docker-compose.yml

sftpコマンドのワンライナーをcommandに埋めるのはしんどいのでbatchfileをreadします.

version: "3"
services:
  sftpclient:
    image: chartedcode/alpine-sftp-client
    command: sshpass sftp -i /tmp/.ssh/id_rsa -oStrictHostKeyChecking=no -b /tmp/sftp_job_script sftpsaikou@127.0.0.1
    volumes:
      - .:/tmp:ro
      - /mnt/data:/mnt/data
  subsequent_job_a:
    image: ubuntu:latest
    command: ls -l /mnt/data && echo "sftpは最高だぜ"
    depends_on:
      -  sftpclient
    volumes:
      - /mnt/data:/mnt/data
  • sftp_job_script

docker-composeと同一ディレクトリにbatchfileを作ります。(パス分けてもいいんですけども)

ls
lcd /mnt/data
get unko.csv
bye

実行例

$ docker-compose -f docker-compose.yml up
Starting jobuser_sftpclient_1 ... done
Recreating jobuser_cron_job_1 ... done
Attaching to jobuser_sftpclient_1, jobuser_cron_job_1
sftpclient_1  | sftp> ls
sftpclient_1  | sftp> lcd /mnt/data
sftpclient_1  | sftp> get unko.csv
sftpclient_1  | sftp> bye
jobuser_sftpclient_1 exited with code 0
cron_job_1    | total 0
cron_job_1    | -rw-r--r-- 1 root root 0 Apr 10 16:49 unko.csv
cron_job_1    | sftpは最高だぜ
jobuser_cron_job_1 exited with code 0

良いデータ連携ライフを!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerでiOSの疑似脱獄

iOSでは、制約に縛られることがかなり多いわけですが、dockerを使って、iOSを乗っ取れるかもしれない、などと夢想しています。

たまたまiPhoneのマイク、スピーカーを共有する必要のあるプロジェクトにかかわっていまして、アイデアの一つとして、可能性を追いかけたく思っています。

皆様のご意見等いただければ幸いです。:smile:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのGUI管理ツール調査

Kitematic

GitHubの最終更新は、2019年2月4日
ライセンスは、Apache License 2.0
0.17.7 (2019年2月4日)

読み方は、カイトマティック
Windows、および、macOSでDockerの導入・操作ができるツール

公式サイト
GitHub

Rancher

GitHubの最終更新は、2019年4月10日
ライセンスは、Apache License 2.0
2.2.2-rc8 (2019年4月10日)

読み方は、ランチャー?

公式サイト
GitHub
Rancher 2.x image
Rancher 1.x image
RANCHER JP
DockerのGUI管理ツール Rancherを試してみる
話題のKubernetesをRancher2.0で 便利に使う!
Rancherでコンテナサービスをデプロイしてみる
RancherでKubernetesクラスタを作る
Rancher2.1でKubernetes環境を構築する
Rancher2.0を使って超高速にkubernetesクラスタを構築した時のメモ
シングルノードにkubernetesとRancher 2.0を導入して、コンテナ管理してみた
Rancher 2.0で複数のKubernetes環境を管理してみる
Rancher/Kubernetes入門ハンズオン資料~第2回さくらとコンテナの夕べ #さくらの夕べ
Rancher 2.0 入門!!! Kubernetesクラスタの可視化!!!
複数の物理ホストの Debian9 上に Kubernetes (k8s) クラスタを Rancher で立ち上げる
Rancherでコンテナ化に挫折しました
Rancher 構築メモ

Portainer

GitHubの最終更新は、2019年4月8日
ライセンスは、zlib License
利用条件が緩いzlibライセンスについて
0.10.1 (2016年4月3日)

読み方は、ポーテイナー?
Windows、Linux、macOS

公式サイト
GitHub
Portainer - シンプルなDockerイメージ/コンテナ管理
Docker初心者でも安心!Portainerを使ってイメージやコンテナを管理する
Portainer でできること
Portainerを利用して個人環境のコンテナ管理をする
portainerであれこれ試した話
portainerを使ってローカル環境でDockerコンテナの管理
自宅での技術検証がより楽しくなる? portainer で Docker コンテナ管理
[2019年1月版]WSL + Ubuntu + Docker で Portainer
portainer(CentOS7)構築メモ
CoreOSとPortainerで簡単コンテナ管理
Docker のイメージやコンテナをGUIで管理できる Portainer のインストール
Docker を管理する Webアプリ Portainer を Docker Compose で起動する
Docker 管理ツール Portainer を導入する

Docker

# portainerが動いているDockerホストを管理する場合(Linux or Mac)
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

# リモートのDockerホストを管理する場合
$ docker run -d -p 9000:9000 portainer/portainer

Panamax-ui

GitHubの最終更新は、2016年2月24日
ライセンスは、Apache License 2.0
0.2.21 (2015年7月22日)

GitHub
centurylink/panamax-ui
Panamax - WebベースのDocker管理
コンテナ管理ツール「Panamax」をCoreOSと組み合わせて使ってみよう
Panamax をもっと簡単に Vagrant だけで使いたい

simple-docker-ui

GitHubの最終更新は、2017年8月8日
ライセンスは、MIT License
0.5.5 (2017年2月21日)

GitHub
Dockerのイメージ、コンテナ管理を手軽にしてくれる「Simple Docker UI」レビュー
ブラウザ経由のDocker管理UI Simple Docker UI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows 10 Educationにしてみた

事の発端

周りの人を含めてみんなDockerやってるし自分も流行に乗りたいなーDockerでドカドカしたいなー
→Docker for Windowsを使うためにはHyper-Vが必要
→Windows 10 HomeではHyper-Vを有効化できない
というところまで調べて諦めていたのですが、MicrosoftのMicrosoft Imagineというプログラムの一環で学生であればWindows 10 Educationが無料で使える(=Hyper-Vの有効化ができる)とのことだったので、実際にやってみました。

Azure for Studentのアカウントを作成する

はじめに、Microsoftのアカウントを持っていない人は以下のリンクから新規作成を行いましょう。https://account.microsoft.com/account?lang=ja-JP

次に、https://azure.microsoft.com/ja-jp/free/students/ このページへアクセスし、今すぐアクティブ化をクリックします。
すると以下のようなページに飛ぶので、自分の学校でのメールを入力していきます。
azure2.PNG

メールアドレスを2回入力したら教育機関ステータスの確認をクリックし、きちんと学校の有効なメールアドレスであることを証明します。(自分の場合はメールが届きそのメールに添付されていたリンクを押して証明しました。)
(学生であってもメールアドレスを取得することがない学校の場合は取得できるかわからないです・・・)

学生であることを証明したら自分の情報を入力していきます。
azure3.PNG

最後に電話番号で本人確認(?)を行うと完了です。(画面に従うだけなので特に難しいところはないと思います。)
以下の画面に到達すれば成功です。
azure4.PNG

Windows 10 Education用のライセンスキーの入手

上記の画面にたどり着いたらそのまま右に表示されているソフトウェアを取得の横の方にあるすべて表示をクリックし、一覧の中からWindows 10 Educationを探します。(検索を使うといいかもしれません)
その後キーを表示し、コピーします。
azure7.PNG

キーをコピーしたら次は左下のスタートメニューから設定を開きます。設定→更新とセキュリティ→ライセンス認証へ進み、プロダクトキーを変更しますから先ほどコピーしたプロダクトキーをペーストし次へを押しプロダクトキーを更新します。

するとWindowsのインストールが始まるので、しばらく待って再起動したら成功です。PCのプロパティから現在のWindowsのバージョンを確認してみます。
azure6.PNG
無事にWindows 10 Educationに変更できていることが確認できました。

また、事の発端であったHyper-Vも有効にできることが確認できました。
Hyper.PNG

まとめ

これで自分もDockerでドカドカできるぞー!
Qiitaの投稿初めてなので読みづらかったり変なところがあったらごめんなさい。

Hyper-Vの有効化からDockerの導入はたくさんの人がわかりやすく説明してくださっているのでここでは割愛させていただきます。

この記事が少しでもお役に立てれば光栄です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む