2016年12月13日星期二

OpenShift_014:使用 S2I 创建一个复杂应用(PHP + MySQL)

环境:OSE 3.0.1

本文我们创建一个复杂一些的应用,PHP + MySQL。
这里我们使用多个资源定义文件一步一步创建需要的对象,虽然麻烦,但有助于了解 OpenShift 构建应用的整个过程。

1. 为 MySQL 配置 NFS
[root@master ~]# mkdir /var/export/instructor
[root@master ~]# chown nfsnobody:nfsnobody /var/export/instructor
[root@master ~]# chmod 700 /var/export/instructor

[root@master ~]# vim /etc/exports
/var/export/instructor *(rw,async,all_squash)

[root@master ~]# exportfs -a

2. 创建 PV
[root@master ~]# oc login -u system:admin

[root@master ~]# oc create -f mysqlPV.json

mysqlPV.json 内容如下:

{
    "apiVersion": "v1",
    "kind": "PersistentVolume",
    "metadata": {
        "name": "mysqldb-instructor-volume",
        "labels": {
            "name": "mysqldb"
        }
    },
    "spec": {
        "capacity": {
            "storage": "1Gi"
        },
        "accessModes": [ "ReadWriteMany" ],
        "nfs": {
            "path": "/var/export/instructor",
            "server": "master.pod0.example.com"
        }
    }
}

3. 创建 project
[student@workstation ~]$ oc login -u devloper -p openshift
[student@workstation ~]$ oc new-project instructor

4. 创建 MySQL Database pod
[student@workstation ~]$ oc create -f mysqldb-pvclaim.json
mysqldb-pvclaim.json 内容如下:
{
    "apiVersion": "v1",
    "kind": "PersistentVolumeClaim",
    "metadata": {
        "name": "mysqldb-instructor-pvclaim",
        "labels": {
            "name": "mysqldb"
        }
    },
    "spec": {
        "accessModes": [ "ReadWriteMany" ],
        "resources": {
            "requests": {
                "storage": "1Gi"
            }
        }
    }
}

[student@workstation ~]$ oc create -f mysqldb-pod-pv.json
mysqldb-pod-pv.json 内容如下:
{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "mysqldb",
        "labels": {
            "name": "mysqldb"
        }
    },
    "spec": {
        "containers": [
            {
                "name": "mysqldb",
                "image": "openshift3/mysql-55-rhel7",
                "ports": [
                    {
                        "containerPort": 3306
                    }
                ],
                "env": [
                    {
                        "name": "MYSQL_USER",
                        "value": "instructor"
                    },
                    {
                        "name": "MYSQL_PASSWORD",
                        "value": "password"
                    },
                    {
                        "name": "MYSQL_DATABASE",
                        "value": "instructor"
                    }
                ],
                "volumeMounts": [
                    {
                        "name": "data",
                        "mountPath": "/var/lib/mysql/data"
                    }
                ]
            }
        ],
        "volumes": [
            {
                "name": "data",
                "persistentVolumeClaim": {
                    "claimName": "mysqldb-instructor-pvclaim"
                }
            }
        ]
    }
}

说明:
(1)MYSQL_USER、MYSQL_PASSWORD、MYSQL_DATABASE 定义在 MySQL pod 上的环境变量,是在创建该 pod 时要使用的。
(2)使用 oc env pods --all --list 可以查看当前 project 中所有 pod 上定义的环境变量。
(3)注意,env 定义的环境变量是给该 pod 自己使用的,不是暴露出来给其它 pod 使用的。

[student@workstation ~]$ oc create -f mysqldb-service.json
mysqldb-service.json 内容如下:
{
        "kind": "Service",
        "apiVersion": "v1",
        "metadata": {
        "name": "mysqldb"
        },
        "spec": {
                "ports": [
                {
                        "port": 3306,
                        "targetPort": 3306
                }
                ],
                "selector": {
                        "name": "mysqldb"
                }
        }
}

说明:
(1)"port": 3306, 是 service 暴露的端口
(2)"targetPort": 3306 对应的 pod 将要暴露的端口,这个值要和 pod 中的定义一致。
(3) "selector": "name": "mysqldb",表明所有和该 selector 匹配的 pod,都将作为该 service 的一个 end point。

除了根据 service 资源文件创建 service 之外,还可以从已有 pod 创建 service,比如:
$ oc expose pod mysqldb --name=mysqldb --selector='name=mysqldb'。

另外,每一个 project 中的 service 暴露后,会释放两个环境变量给所有 pod,可以在 pod 的程序中使用。
SVCNAME_SERVICE_HOST 和 SVCNAME_SERVICE_PORT。
对于本应用,这里的两个变量是 MYSQLDB_SERVICE_HOST=172.30.15.149 和  MYSQLDB_SERVICE_PORT=3306。

5. Poplulate MySQL Database
[root@master ~]# oc get services -n instructor
NAME      LABELS          SELECTOR        IP(S)                    PORT(S)
mysqldb   <none>    name=mysqldb   172.30.12.107      3306/TCP

[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 < instructor.sql

instructor.sql 内容如下:
USE instructor;

--
-- Table structure for table `instructors`
--


CREATE TABLE IF NOT EXISTS `instructors` (
  `instructorNumber` int(11) NOT NULL AUTO_INCREMENT,
  `instructorName` varchar(50) NOT NULL,
  `email` varchar(50) NOT NULL,
  `city` varchar(50) NOT NULL,
  `state` varchar(50) DEFAULT NULL,
  `postalCode` varchar(15) DEFAULT NULL,
  `country` varchar(50) NOT NULL,
  PRIMARY KEY (`instructorNumber`)
);

--
-- Dumping data for table `instructors`
--

INSERT INTO `instructors` (`instructorNumber`, `instructorName`, `email`, `city`, `state`, `postalCode`, `country`) VALUES
(103, 'Douglas Silva', 'dsilva@redhat.com', 'Brasilia','DF', '72000-000', 'Brasil'),
(112, 'Fernando Lozano', 'flozano@redhat.com', 'Rio de Janeiro', 'RJ', '22021-000', 'Brasil'),
(114, 'Jim Rigsbee', 'jrigsbee@redhat.com', 'Raleigh', 'NC', '27605', 'United States'),
(123, 'Ricardo Jun Taniguchi', 'jtaniguc@redhat.com', 'Sao Paulo', 'SP', '01310-000', 'Brasil')

[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 instructor
MySQL [instructor]> select * from instructors;

因为 master 机器可以直接访问 service,所以可以按照上述方式访问数据库。
如果在 workstation 机器上,可以使用端口转发方式访问数据库。

[student@workstation ~]$ oc port-forward -p mysqldb 13306:3306

再开一个终端
[student@workstation ~]$ mysql -uinstructor -ppassword -h127.0.0.1 -P13306 < instructor.sql

6. 使用 S2I 创建应用
[student@workstation ~]$ oc new-app php~http://workstation.pod0.example.com/instructor

[student@workstation ~]$ oc expose service instructor --hostname=instructor.cloudapps0.example.com

访问 http://instructor.cloudapps.example.com


7. PHP pod 是怎样访问 MySQL  pod 的?
[student@workstation ~]$ git clone http://workstation.pod0.example.com/instructor

[student@workstation ~]$ cat instructor/services/api.php
     require_once("Rest.inc.php");
   
    class API extends REST {
   
        public $data = "";
       
        //const DB_SERVER = "172.30.160.232";
        const DB_USER = "instructor";
        const DB_PASSWORD = "password";
        const DB = "instructor";

        private $db = NULL;
        private $mysqli = NULL;
        public function __construct(){
            parent::__construct();                // Init parent contructor
            $this->dbConnect();                    // Initiate Database connection
        }
       
        /*
         *  Connect to Database
        */
        private function dbConnect(){
            //$this->mysqli = new mysqli(self::DB_SERVER, self::DB_USER, self::DB_PASSWORD, self::DB);
            $this->mysqli = new mysqli($_ENV["MYSQLDB_SERVICE_HOST"], self::DB_USER, self::DB_PASSWORD, self::DB);
        }
       
        /*
         * Dynmically call the method based on the query string
         */
        public function processApi(){
            $func = strtolower(trim(str_replace("/","",$_REQUEST['x'])));
            if((int)method_exists($this,$func) > 0)
                $this->$func();
            else
                $this->response('',404); // If the method not exist with in this class "Page not found".
        }
               
        private function login(){
            if($this->get_request_method() != "POST"){
                $this->response('',406);
            }
            $email = $this->_request['email'];       
            $password = $this->_request['pwd'];
            if(!empty($email) and !empty($password)){
                if(filter_var($email, FILTER_VALIDATE_EMAIL)){
                    $query="SELECT uid, name, email FROM users WHERE email = '$email' AND password = '".md5($password)."' LIMIT 1";
                    $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);

                    if($r->num_rows > 0) {
                        $result = $r->fetch_assoc();   
                        // If success everythig is good send header as "OK" and user details
                        $this->response($this->json($result), 200);
                    }
                    $this->response('', 204);    // If no records "No Content" status
                }
            }
           
            $error = array('status' => "Failed", "msg" => "Invalid Email address or Password");
            $this->response($this->json($error), 400);
        }
       
        private function instructors(){   
            if($this->get_request_method() != "GET"){
                $this->response('',406);
            }
            $query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c order by c.instructorNumber desc";
            $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);

            if($r->num_rows > 0){
                $result = array();
                while($row = $r->fetch_assoc()){
                    $result[] = $row;
                }
                $this->response($this->json($result), 200); // send user details
            }
            $this->response('',204);    // If no records "No Content" status
        }
        private function instructor(){   
            if($this->get_request_method() != "GET"){
                $this->response('',406);
            }
            $id = (int)$this->_request['id'];
            if($id > 0){   
                $query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c where c.instructorNumber=$id";
                $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
                if($r->num_rows > 0) {
                    $result = $r->fetch_assoc();   
                    $this->response($this->json($result), 200); // send user details
                }
            }
            $this->response('',204);    // If no records "No Content" status
        }
       
        private function insertInstructor(){
            if($this->get_request_method() != "POST"){
                $this->response('',406);
            }

            $instructor = json_decode(file_get_contents("php://input"),true);
            $column_names = array('instructorName', 'email', 'city', 'country');
            $keys = array_keys($instructor);
            $columns = '';
            $values = '';
            foreach($column_names as $desired_key){ // Check the instructor received. If blank insert blank into the array.
               if(!in_array($desired_key, $keys)) {
                       $$desired_key = '';
                }else{
                    $$desired_key = $instructor[$desired_key];
                }
                $columns = $columns.$desired_key.',';
                $values = $values."'".$$desired_key."',";
            }
            $query = "INSERT INTO instructors(".trim($columns,',').") VALUES(".trim($values,',').")";
            if(!empty($instructor)){
                $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
                $success = array('status' => "Success", "msg" => "Instructor Created Successfully.", "data" => $instructor);
                $this->response($this->json($success),200);
            }else
                $this->response('',204);    //"No Content" status
        }
        private function updateInstructor(){
            if($this->get_request_method() != "POST"){
                $this->response('',406);
            }
            $instructor = json_decode(file_get_contents("php://input"),true);
            $id = (int)$instructor['id'];
            $column_names = array('instructorName', 'email', 'city', 'country');
            $keys = array_keys($instructor['instructor']);
            $columns = '';
            $values = '';
            foreach($column_names as $desired_key){ // Check the instructor received. If key does not exist, insert blank into the array.
               if(!in_array($desired_key, $keys)) {
                       $$desired_key = '';
                }else{
                    $$desired_key = $instructor['instructor'][$desired_key];
                }
                $columns = $columns.$desired_key."='".$$desired_key."',";
            }
            $query = "UPDATE instructors SET ".trim($columns,',')." WHERE instructorNumber=$id";
            if(!empty($instructor)){
                $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
                $success = array('status' => "Success", "msg" => "Instructor ".$id." Updated Successfully.", "data" => $instructor);
                $this->response($this->json($success),200);
            }else
                $this->response('',204);    // "No Content" status
        }
       
        private function deleteInstructor(){
            if($this->get_request_method() != "DELETE"){
                $this->response('',406);
            }
            $id = (int)$this->_request['id'];
            if($id > 0){               
                $query="DELETE FROM instructors WHERE instructorNumber = $id";
                $r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
                $success = array('status' => "Success", "msg" => "Successfully deleted one record.");
                $this->response($this->json($success),200);
            }else
                $this->response('',204);    // If no records "No Content" status
        }
       
        /*
         *    Encode array into JSON
        */
        private function json($data){
            if(is_array($data)){
                return json_encode($data);
            }
        }
    }
   
    // Initiiate Library
   
    $api = new API;
    $api->processApi();
?>

没有评论: