引言
在 Dokcer学习一 已经可以简单的安装一个Docker 容器并暴露某个端口给外部使用了。
安装不是目的, 应用才是王道。 发布一个快速部署的Web容器才是Docker的用处。
当前有需求:
Tomcat启动在Docker容器中,它需要告知注册中心自己暴露对外的Host以及Port。
传统的方式只可以获取到Tomcat启动时绑定的端口, 需要获取Docker对外映射的宿主机的Host以及开放的端口。
环境部署
应用程序目录:/opt
安装JDK.
# wget http://download.oracle.com/otn-pub/java/jdk/8u91-b14/jdk-8u91-linux-x64.tar.gz
# ls
jdk-8u91-linux-x64.tar.gz
# tar -xzvf jdk-8u91-linux-x64.tar.gz
# jdk1.8.0_91/bin/java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
安装Tomcat
# wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.0/bin/apache-tomcat-8.5.0.tar.gz
# tar -xzvf apache-tomcat-8.5.0.tar.gz
# apache-tomcat-8.5.0/bin/startup.sh
Using CATALINA_BASE: /opt/apache-tomcat-8.5.0
Using CATALINA_HOME: /opt/apache-tomcat-8.5.0
Using CATALINA_TMPDIR: /opt/apache-tomcat-8.5.0/temp
Using JRE_HOME: /opt/jdk1.8.0_91
Using CLASSPATH: /opt/apache-tomcat-8.5.0/bin/bootstrap.jar:/opt/apache-tomcat-8.5.0/bin/tomcat-juli.jar
Tomcat started.
# curl -I -s 127.0.0.1:8080
HTTP/1.1 200
将程序委托给Supervisord管理
# echo '#! /bin/bash
export JAVA_HOME=/opt/jdk1.8.0_91
export CLASSPATH=.:$JAVA_HOME:/lib:$JAVA_HOME/jre/lib:
export PATH=$PATH:$JAVA_HOME/bin
/opt/apache-tomcat-8.5.0/bin/startup.sh' > /etc/supervisor/conf.d/tomcat.sh
# echo '[supervisord]
nodaemon=true
[program:tomcat]
command=/etc/supervisor/conf.d/tomcat.sh' > /etc/supervisor/conf.d/tomcat.conf
当然, 每次变更一定要记住提交。
# docker commit acf27ff06413 tomcat
此时就可以开始访问Tomcat服务了。
# docker run -it -d -p 22 -p 8080 tomcat /usr/bin/supervisord
# docker ps -a
IMAGE STATUS PORTS
tomcat Up 6 seconds 0.0.0.0:32774->22/tcp, 0.0.0.0:32773->8080/tcp
# curl -I 127.0.0.1:32773
HTTP/1.1 200
将Docker镜像导出以便于本地使用
# docker export d3219e60d2b2 > docker_tomcat.tar
# gzip docker_tomcat.tar
# docker start d3219e60d2b2
# docker ps -a
CONTAINER ID COMMAND PORTS
d3219e60d2b2 "/usr/bin/supervisord" 0.0.0.0:32776->22/tcp, 0.0.0.0:32775->8080/tcp
# docker cp docker_tomcat.tar.gz 63730c15f13e:/opt/apache-tomcat-8.5.0/webapps/ROOT/docker_tomcat.html
# wget -b -c 公网ip:32775/docker_tomcat.html > docker_tomcat.tar.gz
此处分享:
服务器选购需谨慎。 上方下载共计耗时12小时。 选择更适合自己的方式很重要。
Web部署
源码展示 (非必须)
UnmodifiedSet.java
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
/**
*
* 毕竟将Guava引入到pom里面去也是件比较麻烦的事情, 因此直接给定一个默认的实现
*
* @author http://blog.csdn.net/qyp199312/
* @since 2016-05-09
*/
public class UnmodifiedSet<E> implements Set<E> {
protected transient int size = 0;
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<E> iterator() {
throw new UnsupportedOperationException();
}
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
@Override
public <T> T[] toArray(T[] a) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(E e) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends E> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}
UnmodifiedSet.java
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
*
* 毕竟将Guava引入到pom里面去也是件比较麻烦的事情, 因此直接给定一个默认的实现
*
* @see java.util.Collections.UnmodifiableMap
* @author http://blog.csdn.net/qyp199312/
* @since 2016-05-09
*/
public class UnmodifiableMap<K, V> implements Map<K, V> {
@Override
public int size() {
return entrySet().size();
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsKey(Object key) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public V get(Object key) {
throw new UnsupportedOperationException();
}
@Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public V remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
@Override
public Collection<V> values() {
throw new UnsupportedOperationException();
}
@Override
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (! i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(',').append(' ');
}
}
}
EnvironmentUtils.java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* 可获取到 java "-D key=value" 的环境变量
*
* @author http://blog.csdn.net/qyp199312/
* @since 2016-05-09
*/
public class EnvironmentUtils {
private static final transient Map<String, String> enVir = System.getenv();
private static final transient Map<Object, Object> profile = System.getProperties();
private static final ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<String, String>();
private static final Set<String> fileName = new HashSet<String>();
static {
cp(enVir, properties);
cp(profile, properties);
}
private static final <K, V> void cp(Map<K, V> source,
Map<String, String> target) {
for (Entry<K, V> entry : source.entrySet()) {
target.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
}
public static Map<String, String> environment() {
return new UnmodifiableMap<String, String>() {
@Override
public int size() {
return properties.size();
}
@Override
public String get(Object k) {
return properties.get(k);
}
@Override
public boolean containsKey(Object k) {
return properties.containsKey(k);
}
@Override
public Set<String> keySet() {
return new UnmodifiedSet<String>() {
};
}
};
}
public static Map<String, String> load(String filePath) throws IOException {
if (fileName.contains(filePath)) {
return environment();
}
InputStream is = EnvironmentUtils.class.getResourceAsStream(filePath);
if (is == null) {
is = EnvironmentUtils.class.getClassLoader().getResourceAsStream(filePath);
}
if (is == null) {
File file = new File(filePath);
if (file.exists()) {
is = new FileInputStream(file);
}
}
if (is != null) {
Properties p = new Properties();
p.load(is);
cp(p, properties);
fileName.add(filePath);
is.close();
}
return environment();
}
public static String get(String k) {
return properties.get(k);
}
public static String getWithReplace(String k, String replace) {
String v;
return (v = properties.get(k)) == null ? replace : v;
}
public static String getIfNull(String k) {
return getWithReplace(k, "");
}
/**
* 获取字段模糊匹配开头Value <p>
* 不使用缓存, 在多线程环境下执行 #load 的时候可能会出现数据错误
*
* @param k 模糊匹配的起始
* @return
*/
public static Map<String, String> getStart(final String k) {
return new UnmodifiableMap<String, String>() {
@Override
public String get(Object key) {
if (String.valueOf(key).startsWith(k)) {
return properties.get(k);
}
return null;
}
@Override
public Set<Entry<String, String>> entrySet() {
return new UnmodifiedSet<Entry<String, String>>() {
{
Iterator it = iterator();
while (it.hasNext()) {
super.size ++;
}
}
public Iterator<Entry<String, String>> iterator() {
return new Iterator<Entry<String, String>>() {
private Iterator<Entry<String, String>> data = properties.entrySet().iterator();
private Entry<String, String> next = null;
private int size = properties.size();
private int current = size;
@Override
public boolean hasNext() {
return (next = nextData()) != null;
}
public Entry<String, String> nextData() {
int size = this.size;
while (data.hasNext()) {
if (current != size) {
size --;
continue;
}
Entry<String, String> next = data.next();
if (next.getKey().startsWith(k)) {
current --;
return next;
}
}
return null;
}
@Override
public Entry<String, String> next() {
return next;
}
public void remove() {
throw new UnsupportedOperationException("remove");
}
};
}
};
}
};
}
}
GateController.java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class GateController {
@RequestMapping("key")
@ResponseBody
public String key(@RequestParam("key") String key) {
return EnvironmentUtils.get(key);
}
@RequestMapping("start")
@ResponseBody
public String start(@RequestParam("key") String key) {
return String.valueOf(EnvironmentUtils.getStart(key));
}
}
开始访问
部署并启动服务
# docker run -it -d -p 22 -p 8080 tomcat /usr/bin/supervisord
# docker ps -a
IMAGE COMMAND PORTS
tomcat "/usr/bin/supervisord" 0.0.0.0:32769->22/tcp, 0.0.0.0:32768->8080/tcp
# docker cp web2-1.0-SNAPSHOT.war ccb21ae25ca7:/opt/apache-tomcat-8.5.0/webapps/
访问示例
# curl 127.0.0.1:32768/web2-1.0-SNAPSHOT/start?key=java.vm
{java.vm.vendor=Oracle Corporation, java.vm.specification.version=1.8, java.vm.specification.vendor=Oracle Corporation, java.vm.name=Java HotSpot(TM) 64-Bit Server VM, java.vm.specification.name=Java Virtual Machine Specification, java.vm.info=mixed mode, java.vm.version=25.91-b14}
# curl 127.0.0.1:32768/web2-1.0-SNAPSHOT/key?key=java.vm.info
mixed mode
Get Port(Tomcat Only)
让Web获取Docker运行容器的端口
@RequestMapping("docker_port")
@ResponseBody
public String docker_port()
throws MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException,
InstanceNotFoundException {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> set = server.queryNames(
new ObjectName("*:type=Connector,*"),
Query.match(
Query.attr("protocol"),
Query.value("HTTP/1.1"))
);
if (set != null) {
for (ObjectName name : set) {
Object scheme = server.getAttribute(name, "scheme");
String port = name.getKeyProperty("port");
if (scheme != null && "http".equalsIgnoreCase(scheme.toString())) {
return port;
}
}
}
return String.valueOf(EnvironmentUtils.getStart("port"));
}
测试结果:
# curl 127.0.0.1:49164/navigation-0.0.1-SNAPSHOT/docker_port
8080
让Web获取宿主机端口
web程序无法获取容器绑定端口。但是可以通过一定的方式得知:
- Tomcat遵循JMX规范,启动时将告知JMX启动端口以及Host,由此获取即可
- 将启动端口以及Host配置在环境变量里面,直接获取
脚本:
$ 在 bin/startup.sh倒数第二行加入:
export JAVA_OPTS=" $JAVA_OPTS -Dhost_port=10086 -Dhost_mapping=domain.com"
测试结果:
# curl 127.0.0.1:49164/navigation-0.0.1-SNAPSHOT/key?key=host_mapping
domain.com
# curl 127.0.0.1:49164/navigation-0.0.1-SNAPSHOT/key?key=host_port
10086
通过发布时加入环境变量的方式可以轻松获取到宿主机Host以及映射给Docker Tomcat的端口。