2017年7月12日星期三

OpenShift_084:安装配置 JBoss AMQ(Route SSL)

环境:OCP 3.5

本文在 OpenShift 平台上部署一个 JBoss AMQ,配置消息持久化,通过 Route SSL 访问。

1. 修改默认的 template amq62-persistent-ssl 
oc edit template amq62-persistent-ssl -n openshift

kind: ImageStreamTag
name: jboss-amq-62:1.3
改为
kind: ImageStreamTag
name: jboss-amq-62:latest

为方便起见,建议把所有有关 amq62 的 template 都修改一遍。
oc edit template amq62-basic -n openshift
oc edit template amq62-persistent -n openshift
oc edit template amq62-persistent-ssl -n openshift
oc edit template amq62-ssl -n openshift

2. 创建 NFS Server(在 Registry 机器上操作)
yum install –y nfs-utils

export volname=amq-vol
mkdir -p /srv/nfs/${volname}
chown nfsnobody:nfsnobody /srv/nfs/${volname}
chmod 700 /srv/nfs/${volname}
echo "/srv/nfs/${volname} *(rw,sync,all_squash)" >> /etc/exports
systemctl enable nfs-server
systemctl restart rpcbind
systemctl restart nfs-server nfs-lock nfs-idmap

3. 测试 NFS Server 工作是否正常 (在 Node1/Node2 机器上操作)
export volname=amq-vol
mkdir -p /mnt/nfs
mount -t nfs registry.example.com:/srv/nfs/${volname} /mnt/nfs
umount /mnt/nfs

4. 创建 PV (在 Master 机器上操作,如果出错,此步需要重做)
echo '{
  "apiVersion": "v1",
  "kind": "PersistentVolume",
  "metadata": {
    "name": "amq-volume"
  },
  "spec": {
    "capacity": {
        "storage": "512Mi"
        },
    "accessModes": [ "ReadWriteMany" ],
    "nfs": {
        "path": "/srv/nfs/amq-vol",
        "server": "registry.example.com"
    },
    "persistentVolumeReclaimPolicy": "Recycle"
  }
}' | oc create -f -

如果出错,执行以下命令清理,然后重做

5. 创建各种对象
(1)oc new-project amq-demo
(2)创建 service account,用于 A-MQ 部署
  echo '{"kind": "ServiceAccount", "apiVersion": "v1", "metadata": {"name": "amq-service-account"}}' | oc create -f -
(3)给 amq-service-account 赋予 view 权限
  oc policy add-role-to-user view system:serviceaccount:amq-demo:amq-service-account
(4)A-MQ 需要一个 broker keyStore,一个 client keyStore,一个 client trustStore 其中包括 broker keyStore
(4.1)生成一个自签名的证书,作为 broker keyStore
keytool -genkey -dname "CN=Ma Ping,OU=SA,O=Redhat,L=CY,ST=Beijing,C=CN" -alias broker -keyalg RSA -keysize 1024 -keystore broker.ks -keypass redhat -storepass redhat -validity 365
(4.2)导出刚刚生成的 broker keyStore 证书,以便分享
keytool -export -alias broker -keystore broker.ks -storepass redhat -file broker_cert
(4.3)生成一个自签名的证书,作为 client keyStore
keytool -genkey -dname "CN=Ma Ping,OU=SA,O=Redhat,L=CY,ST=Beijing,C=CN" -alias client -keyalg RSA -keysize 1024 -keystore client.ks -keypass redhat -storepass redhat -validity 365
(4.4)创建 client trustStore,并导入之前导出的 broker 证书
keytool -import -alias broker -keystore client.ts -storepass redhat -file broker_cert
(4.5)使用 broker keyStore 创建 secret
oc secrets new amq-app-secret broker.ks
(4.6)把 secret 添加到之前创建的 service account
oc secrets add sa/amq-service-account secret/amq-app-secret

6. 部署
为简单起见,基于 amq62-persistent-ssl template,创建 my-amq62-persistent-ssl
oc get template amq62-persistent-ssl -n openshift -o json > my-amq62-persistent-ssl.json
去掉不必要的 service,只保留 broker-amq-tcp 和 broker-amq-tcp-ssl。
修改后,my-amq62-persistent-ssl.json 内容如下:
{
    "apiVersion": "v1",
    "kind": "Template",
    "labels": {
        "template": "my-amq62-persistent-ssl",
        "xpaas": "1.3.1"
    },
    "metadata": {
        "annotations": {
            "description": "Application template for JBoss A-MQ brokers. These are deployed as standalone and use persistent storage for saving messages. This template supports SSL and requires usage of OpenShift secrets.",
            "iconClass": "icon-jboss",
            "tags": "messaging,amq,jboss,xpaas",
            "version": "1.3.1"
        },
        "creationTimestamp": "2017-05-24T06:57:08Z",
        "name": "my-amq62-persistent-ssl",
        "namespace": "openshift",
        "resourceVersion": "20544",
        "selfLink": "/oapi/v1/namespaces/openshift/templates/my-amq62-persistent-ssl",
        "uid": "346b140a-404e-11e7-999f-080027873f56"
    },
    "objects": [
        {
            "apiVersion": "v1",
            "kind": "Service",
            "metadata": {
                "annotations": {
                    "description": "The broker's OpenWire port."
                },
                "labels": {
                    "application": "${APPLICATION_NAME}"
                },
                "name": "${APPLICATION_NAME}-amq-tcp"
            },
            "spec": {
                "ports": [
                    {
                        "port": 61616,
                        "targetPort": 61616
                    }
                ],
                "selector": {
                    "deploymentConfig": "${APPLICATION_NAME}-amq"
                }
            }
        },
        {
            "apiVersion": "v1",
            "kind": "Service",
            "metadata": {
                "annotations": {
                    "description": "The broker's OpenWire (SSL) port."
                },
                "labels": {
                    "application": "${APPLICATION_NAME}"
                },
                "name": "${APPLICATION_NAME}-amq-tcp-ssl"
            },
            "spec": {
                "ports": [
                    {
                        "port": 61617,
                        "targetPort": 61617
                    }
                ],
                "selector": {
                    "deploymentConfig": "${APPLICATION_NAME}-amq"
                }
            }
        },
        {
            "apiVersion": "v1",
            "kind": "DeploymentConfig",
            "metadata": {
                "labels": {
                    "application": "${APPLICATION_NAME}"
                },
                "name": "${APPLICATION_NAME}-amq"
            },
            "spec": {
                "replicas": 1,
                "selector": {
                    "deploymentConfig": "${APPLICATION_NAME}-amq"
                },
                "strategy": {
                    "rollingParams": {
                        "maxSurge": 0
                    },
                    "type": "Rolling"
                },
                "template": {
                    "metadata": {
                        "labels": {
                            "application": "${APPLICATION_NAME}",
                            "deploymentConfig": "${APPLICATION_NAME}-amq"
                        },
                        "name": "${APPLICATION_NAME}-amq"
                    },
                    "spec": {
                        "containers": [
                            {
                                "env": [
                                    {
                                        "name": "AMQ_USER",
                                        "value": "${MQ_USERNAME}"
                                    },
                                    {
                                        "name": "AMQ_PASSWORD",
                                        "value": "${MQ_PASSWORD}"
                                    },
                                    {
                                        "name": "AMQ_TRANSPORTS",
                                        "value": "${MQ_PROTOCOL}"
                                    },
                                    {
                                        "name": "AMQ_QUEUES",
                                        "value": "${MQ_QUEUES}"
                                    },
                                    {
                                        "name": "AMQ_TOPICS",
                                        "value": "${MQ_TOPICS}"
                                    },
                                    {
                                        "name": "MQ_SERIALIZABLE_PACKAGES",
                                        "value": "${MQ_SERIALIZABLE_PACKAGES}"
                                    },
                                    {
                                        "name": "AMQ_SPLIT",
                                        "value": "${AMQ_SPLIT}"
                                    },
                                    {
                                        "name": "AMQ_MESH_DISCOVERY_TYPE",
                                        "value": "${AMQ_MESH_DISCOVERY_TYPE}"
                                    },
                                    {
                                        "name": "AMQ_MESH_SERVICE_NAME",
                                        "value": "${APPLICATION_NAME}-amq-tcp"
                                    },
                                    {
                                        "name": "AMQ_MESH_SERVICE_NAMESPACE",
                                        "valueFrom": {
                                            "fieldRef": {
                                                "fieldPath": "metadata.namespace"
                                            }
                                        }
                                    },
                                    {
                                        "name": "AMQ_KEYSTORE_TRUSTSTORE_DIR",
                                        "value": "/etc/amq-secret-volume"
                                    },
                                    {
                                        "name": "AMQ_TRUSTSTORE",
                                        "value": "${AMQ_TRUSTSTORE}"
                                    },
                                    {
                                        "name": "AMQ_TRUSTSTORE_PASSWORD",
                                        "value": "${AMQ_TRUSTSTORE_PASSWORD}"
                                    },
                                    {
                                        "name": "AMQ_KEYSTORE",
                                        "value": "${AMQ_KEYSTORE}"
                                    },
                                    {
                                        "name": "AMQ_KEYSTORE_PASSWORD",
                                        "value": "${AMQ_KEYSTORE_PASSWORD}"
                                    },
                                    {
                                        "name": "AMQ_STORAGE_USAGE_LIMIT",
                                        "value": "${AMQ_STORAGE_USAGE_LIMIT}"
                                    }
                                ],
                                "image": "jboss-amq-62",
                                "imagePullPolicy": "Always",
                                "name": "${APPLICATION_NAME}-amq",
                                "ports": [
                                    {
                                        "containerPort": 8778,
                                        "name": "jolokia",
                                        "protocol": "TCP"
                                    },
                                    {
                                        "containerPort": 61616,
                                        "name": "tcp",
                                        "protocol": "TCP"
                                    },
                                    {
                                        "containerPort": 61617,
                                        "name": "tcp-ssl",
                                        "protocol": "TCP"
                                    }
                                ],
                                "readinessProbe": {
                                    "exec": {
                                        "command": [
                                            "/bin/bash",
                                            "-c",
                                            "/opt/amq/bin/readinessProbe.sh"
                                        ]
                                    }
                                },
                                "volumeMounts": [
                                    {
                                        "mountPath": "/etc/amq-secret-volume",
                                        "name": "broker-secret-volume",
                                        "readOnly": true
                                    },
                                    {
                                        "mountPath": "/opt/amq/data",
                                        "name": "${APPLICATION_NAME}-amq-pvol"
                                    }
                                ]
                            }
                        ],
                        "serviceAccountName": "amq-service-account",
                        "terminationGracePeriodSeconds": 60,
                        "volumes": [
                            {
                                "name": "broker-secret-volume",
                                "secret": {
                                    "secretName": "${AMQ_SECRET}"
                                }
                            },
                            {
                                "name": "${APPLICATION_NAME}-amq-pvol",
                                "persistentVolumeClaim": {
                                    "claimName": "${APPLICATION_NAME}-amq-claim"
                                }
                            }
                        ]
                    }
                },
                "triggers": [
                    {
                        "imageChangeParams": {
                            "automatic": true,
                            "containerNames": [
                                "${APPLICATION_NAME}-amq"
                            ],
                            "from": {
                                "kind": "ImageStreamTag",
                                "name": "jboss-amq-62:latest",
                                "namespace": "${IMAGE_STREAM_NAMESPACE}"
                            }
                        },
                        "type": "ImageChange"
                    },
                    {
                        "type": "ConfigChange"
                    }
                ]
            }
        },
        {
            "apiVersion": "v1",
            "kind": "PersistentVolumeClaim",
            "metadata": {
                "labels": {
                    "application": "${APPLICATION_NAME}"
                },
                "name": "${APPLICATION_NAME}-amq-claim"
            },
            "spec": {
                "accessModes": [
                    "ReadWriteMany"
                ],
                "resources": {
                    "requests": {
                        "storage": "${VOLUME_CAPACITY}"
                    }
                }
            }
        }
    ],
    "parameters": [
        {
            "description": "The name for the application.",
            "name": "APPLICATION_NAME",
            "required": true,
            "value": "broker"
        },
        {
            "description": "Split the data directory for each node in a mesh.",
            "name": "AMQ_SPLIT",
            "value": "false"
        },
        {
            "description": "Protocols to configure, separated by commas.  Allowed values are: `openwire`, `amqp`, `stomp` and `mqtt`.",
            "name": "MQ_PROTOCOL",
            "value": "openwire"
        },
        {
            "description": "Queue names, separated by commas.  These queues will be automatically created when the broker starts.  If left empty, queues will be still created dynamically.",
            "name": "MQ_QUEUES"
        },
        {
            "description": "Topic names, separated by commas.  These topics will be automatically created when the broker starts.  If left empty, topics will be still created dynamically.",
            "name": "MQ_TOPICS"
        },
        {
            "description": "List of packages that are allowed to be serialized for use in ObjectMessage, separated by commas. If your app doesn't use ObjectMessages, leave this blank. This is a security enforcement. For the rationale, see http://activemq.apache.org/objectmessage.html",
            "name": "MQ_SERIALIZABLE_PACKAGES"
        },
        {
            "description": "Size of persistent storage for database volume.",
            "name": "VOLUME_CAPACITY",
            "required": true,
            "value": "512Mi"
        },
        {
            "description": "User name for standard broker user.  It is required for connecting to the broker.  If left empty, it will be generated.",
            "from": "user[a-zA-Z0-9]{3}",
            "generate": "expression",
            "name": "MQ_USERNAME",
            "value": "admin"
        },
        {
            "description": "Password for standard broker user.  It is required for connecting to the broker.  If left empty, it will be generated.",
            "from": "[a-zA-Z0-9]{8}",
            "generate": "expression",
            "name": "MQ_PASSWORD",
            "value": "admin"
        },
        {
            "description": "The discovery agent type to use for discovering mesh endpoints.  'dns' will use OpenShift's DNS service to resolve endpoints.  'kube' will use Kubernetes REST API to resolve service endpoints.  If using 'kube' the service account for the pod must have the 'view' role, which can be added via 'oc policy add-role-to-user view system:serviceaccount:\u003cnamespace\u003e:default' where \u003cnamespace\u003e is the project namespace.",
            "name": "AMQ_MESH_DISCOVERY_TYPE",
            "value": "kube"
        },
        {
            "description": "Name of a secret containing SSL related files",
            "name": "AMQ_SECRET",
            "required": true,
            "value": "amq-app-secret"
        },
        {
            "description": "SSL trust store filename",
            "name": "AMQ_TRUSTSTORE",
            "required": true,
            "value": "broker.ks"
        },
        {
            "description": "SSL trust store password",
            "name": "AMQ_TRUSTSTORE_PASSWORD",
            "required": true,
            "value": "redhat"
        },
        {
            "description": "SSL key store filename",
            "name": "AMQ_KEYSTORE",
            "required": true,
            "value": "broker.ks"
        },
        {
            "description": "Password for accessing SSL keystore",
            "name": "AMQ_KEYSTORE_PASSWORD",
            "required": true,
            "value": "redhat"
        },
        {
            "description": "The A-MQ storage usage limit",
            "name": "AMQ_STORAGE_USAGE_LIMIT",
            "value": "1 gb"
        },
        {
            "description": "Namespace in which the ImageStreams for Red Hat Middleware images are installed. These ImageStreams are normally installed in the openshift namespace. You should only need to modify this if you've installed the ImageStreams in a different namespace/project.",
            "name": "IMAGE_STREAM_NAMESPACE",
            "required": true,
            "value": "openshift"
        }
    ]
}

(1)部署 my-amq62-persistent-ssl template
Add to Project --> Browse Catalog 输入 amq62,选择 my-amq62-persistent-ssl
其余保持默认,点击 Create,等待 Pods 启动成功。
(2)为 Service:broker-amq-tcp-ssl 创建 Route
(3)修改 Route,勾选中 Secure route,修改 TLS Termination,下拉列表选择 Passthrough。

7. 测试
(1)消息生产者:SSLPublisher.java

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQSslConnectionFactory;

/**
 * 使用SSL连接器 队列消息生产者
 */
public class SSLPublisher {

    public static void main(String[] args) throws JMSException, Exception {
        /*
         * 配置参数 密钥和证书文件的访问目录 密钥密码 SSL链接地址
         */
        String keyStore = "/Users/maping/Apache/broker.ks";
        String trustStore = "/Users/maping/Apache/client.ts";
        String keyStorePassword = "redhat";
        String url = "ssl://broker-amq-tcp-ssl-amq-demo.apps.example.com:443";

        // 创建SSL连接器工厂类
        ActiveMQSslConnectionFactory sslConnectionFactory = new ActiveMQSslConnectionFactory();
        // 设置参数,并加载SSL密钥和证书信息
        sslConnectionFactory.setUserName("admin");
        sslConnectionFactory.setPassword("admin");
        sslConnectionFactory.setBrokerURL(url);
        sslConnectionFactory.setKeyAndTrustManagers(SSLUtils.loadKeyManager(keyStore, keyStorePassword), SSLUtils.loadTrustManager(trustStore),
                new java.security.SecureRandom());

        // 连接ActiveMQ
        Connection conn = sslConnectionFactory.createConnection();
        conn.start();
        Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Destination dest = session.createQueue("sslDemo");

        // 创建消息生产者,发送一条报文消息
        MessageProducer mp = session.createProducer(dest);
        Message msg = session.createTextMessage("Hello SSL!");
        mp.send(msg);
        System.out.println("success");

        // 发送完成,释放连接
        session.close();
        conn.close();
    }
}

(2)消息消费者:SSLListener.java

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQSslConnectionFactory;

/**
 * 使用SSL连接器 队列消息消费者
 */
public class SSLListener {

    public static void main(String[] args) throws JMSException, Exception {
        /*
         * 配置参数 密钥和证书文件的访问目录 密钥密码 SSL链接地址
         */
        String keyStore = "/Users/maping/Apache/broker.ks";
        String trustStore = "/Users/maping/Apache/client.ts";
        String keyStorePassword = "redhat";
        String url = "ssl://broker-amq-tcp-ssl-amq-demo.apps.example.com:443";

        // 创建SSL连接器工厂类
        ActiveMQSslConnectionFactory sslConnectionFactory = new ActiveMQSslConnectionFactory();
        // 设置参数,并加载SSL密钥和证书信息
        sslConnectionFactory.setUserName("admin");
        sslConnectionFactory.setPassword("admin");
        sslConnectionFactory.setBrokerURL(url);
        sslConnectionFactory.setKeyAndTrustManagers(SSLUtils.loadKeyManager(keyStore, keyStorePassword), SSLUtils.loadTrustManager(trustStore),
                new java.security.SecureRandom());

        // 连接ActiveMQ
        Connection conn = sslConnectionFactory.createConnection();
        conn.start();
        Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Destination dest = session.createQueue("sslDemo");
        // 设置消息消费者,在匿名内部类中打印消息内容
        MessageConsumer mc = session.createConsumer(dest);
        mc.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message msg) {
                if (msg instanceof TextMessage) {
                    try {
                        TextMessage tmsg = (TextMessage) msg;
                        System.out.println(tmsg.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println(msg.toString());
                }
            }
        });

        // 不关闭连接,让客户端一直连着ActiveMQ
    }
}
(3)SSL 工具类:SSLListener.java

import java.io.FileInputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

/**
 * SSL 工具类 加载密钥和证书文件
 */
public class SSLUtils {

    /**
     * 加载证书文件
     *
     * @param trustStore
     * @return
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.KeyStoreException
     * @throws java.io.IOException
     * @throws java.security.GeneralSecurityException
     */
    public static TrustManager[] loadTrustManager(String trustStore) throws java.security.NoSuchAlgorithmException, java.security.KeyStoreException,
            java.io.IOException, java.security.GeneralSecurityException {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream(trustStore), null);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        System.out.println("init TrustManagers finish");
        return tmf.getTrustManagers();
    }

    /**
     * 加载密钥文件
     *
     * @param keyStore
     * @param keyStorePassword
     * @return
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.KeyStoreException
     * @throws java.security.GeneralSecurityException
     * @throws java.security.cert.CertificateException
     * @throws java.io.IOException
     * @throws java.security.UnrecoverableKeyException
     */
    public static KeyManager[] loadKeyManager(String keyStore, String keyStorePassword) throws java.security.NoSuchAlgorithmException,
            java.security.KeyStoreException, java.security.GeneralSecurityException, java.security.cert.CertificateException, java.io.IOException,
            java.security.UnrecoverableKeyException {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream(keyStore), keyStorePassword.toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, keyStorePassword.toCharArray());
        System.out.println("init KeyManager finish");
        return kmf.getKeyManagers();
    }
}

(4)在 Web Console 中查看,点击 Pod,点击 Open Java Console


8. 如果有错,执行以下命令清除,然后重做
oc project amq-demo
oc delete pvc broker-amq-claim
oc delete pv amq-volume
oc delete all --all
oc delete template my-amq62-persistent-ssl -n openshift
oc create -f my-amq62-persistent-ssl.json -n openshift

参考文献:
1. http://blog.csdn.net/dailywater/article/details/52833307

没有评论: