Da ich verschiedene Dinge auf dem PI ausprobieren möchte, aber schon ein einigermaßen stabiles System habe, bot es sich an, neue Dinge zunächst in Docker-Containern auszuprobieren, da diese isoliert vom System ablaufen.
Im aktuellen Fall wollte ich ein Java-Programm testen, aber nicht Java-8 (inkl. dessen Abhängigkeiten) auf mein bestehendes System packen.
Also zunächst (und hoffentlich als einziges) Docker installieren:
sudo apt-get install docker.io
Danach das nächst beste Docker-Image (gefunden auf https://hub.docker.com/) ziehen:
docker pull dordoka/rpi-java8
Dies kann eine Weile dauern…
Anschließend einmal ausprobieren:
sudo docker run dordoka/rpi-java8 java -version
Wenn nun etwas in der Art erscheint, hat man sein Java im Docker-Container:
java version "1.8.0_33" Java(TM) SE Runtime Environment (build 1.8.0_33-b05) Java HotSpot(TM) Client VM (build 25.33-b05, mixed mode)
Wenn man aber gerade eh dabei ist sich eine Umgebung einzurichten,
kann man es nicht schaden sich auch noch einen Container zu bauen, in dem maven3 vorhanden ist (aufbauend von dem eben genutzten java-container).
Also ein neues Arbeitsverzeichnis einrichten:
mkdir docker-container cd docker-container mkdir rpi-maven cd rpi-maven
In diesem Ordner wird nun eine neue Datei mit dem Namen „Dockerfile“ angelegt und folgender Inhalt kopiert:
FROM dordoka/rpi-java8 ENV MAVEN_VERSION 3.3.9 RUN apt-get update && apt-get install -y curl RUN curl -fsSL https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar xzf - -C /usr/share \ && mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven \ && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn ENV MAVEN_HOME /usr/share/maven VOLUME /root/.m2 CMD ["mvn"]
Mit diesem Rezept kann man nun ein eigenes Docker-Image bauen:
sudo docker build -t rpi-maven .
Auch das dauert eine Weile. Konkret wird hier curl installiert.
Danach (mittels curl) das gewünschte Maven heruntergeladen (hier: 3.3.9) und einige verschiebe und verlinke Operationen durchgeführt.
Wem das zu lange Dauert, kann auch einfach das fertige Image vom dockerhub nehmen (qnerd/rpi-maven).
Nun sollte man ein Docker-Container haben, in dem Maven verfügbar ist:
sudo docker run rpi-maven mvn -version
Beziehungsweise, wenn man das Image nicht selbst gebaut hat:
sudo docker run qnerd/rpi-maven mvn -version
Nun sollte folgende Ausgabe erscheinen:
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00) Maven home: /usr/share/maven Java version: 1.8.0_33, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-8-oracle/jre Default locale: en_US, platform encoding: ANSI_X3.4-1968 OS name: "linux", version: "4.1.13-v7+", arch: "arm", family: "unix" Das ist ganz praktisch, da ich nun mein eigentliches Java Programm nicht mehr irgendwo runterladen muss, sondern es direkt aus den Quellen bauen kann. Also auch hier wieder ein neue Container: cd .. mkdir hue-bridge cd hue-bridge vi Dockerfile
Im folgenden Rezept installiere ich noch eben git, clone das Repo mit dem zu testenden Programm und baue es mit maven.
Danach sorge ich dafür, dass der Port 8080 von außen verfügbar ist und mache noch etwas umständliches rumgehacke um die aktuelle IP-Adresse zu bestimmen (okay, das meiste davon ist von irgendwo geklaut):
FROM rpi-maven
RUN apt-get install -y git
RUN git clone https://github.com/armzilla/amazon-echo-ha-bridge.git
RUN cd amazon-echo-ha-bridge && mvn install
EXPOSE 8080
CMD ["bash", "-c", "java -jar amazon-echo-ha-bridge/target/amazon-echo-bridge-*.jar --upnp.config.address=$(ip route get 8.8.8.8 | egrep -o '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\s*$')"]
Und wieder bauen lassen:
sudo docker build -t rpi-hue-bridge-sim .
Jetzt wird es Zeit das eigentliche Programm zu starten:
sudo docker run --net="host" rpi-hue-bridge-sim
Beziehungsweise:
sudo docker run --net="host" qnerd/amazon-echo-ha-bridge
Durch die Angabe von --net="host" wird der Docker-Container nicht virtuell in einem privaten Netz mit eigener IP gestartet, sondern verhält sich so, als würde alles innerhalb des Containers direkt auf dem Host ausgeführt. Das macht es einfacher auf den gestarteten Tomcat zuzugreifen.
Der Start sieht wie folgt aus:
pi@raspberrypi:~/hue-bridge $ sudo docker run --net="host" rpi-hue-bridge-sim
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.6.RELEASE)
2015-12-13 20:06:45.291 INFO 1 --- [ main] com.armzilla.ha.SpringbootEntry : Starting SpringbootEntry v0.2.1 on raspberrypi with PID 1 (/data/amazon-echo-ha-bridge/target/amazon-echo-bridge-0.2.1.jar started by root in /data)
2015-12-13 20:06:45.714 INFO 1 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d15089: startup date [Sun Dec 13 20:06:45 UTC 2015]; root of context hierarchy
2015-12-13 20:06:54.192 INFO 1 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-12-13 20:06:58.727 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.scheduling.annotation.SchedulingConfiguration' of type [class org.springframework.scheduling.annotation.SchedulingConfiguration$$EnhancerBySpringCGLIB$$26d4532] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-12-13 20:07:02.862 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-12-13 20:07:04.395 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2015-12-13 20:07:04.404 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.26
2015-12-13 20:07:05.705 INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2015-12-13 20:07:05.707 INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 20015 ms
2015-12-13 20:07:14.242 INFO 1 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2015-12-13 20:07:14.300 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'metricFilter' to: [/*]
2015-12-13 20:07:14.303 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-12-13 20:07:14.305 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-12-13 20:07:14.306 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2015-12-13 20:07:14.308 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'springBootCorsFilter' to: [/*]
2015-12-13 20:07:14.309 INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'applicationContextIdFilter' to: [/*]
2015-12-13 20:07:17.177 INFO 1 --- [ main] org.elasticsearch.node : [Mondo] version[1.3.2], pid[1], build[dee175d/2014-08-13T14:29:30Z]
2015-12-13 20:07:17.180 INFO 1 --- [ main] org.elasticsearch.node : [Mondo] initializing ...
2015-12-13 20:07:17.219 INFO 1 --- [ main] org.elasticsearch.plugins : [Mondo] loaded [], sites []
2015-12-13 20:07:34.985 INFO 1 --- [ main] org.elasticsearch.node : [Mondo] initialized
2015-12-13 20:07:34.986 INFO 1 --- [ main] org.elasticsearch.node : [Mondo] starting ...
2015-12-13 20:07:35.037 INFO 1 --- [ main] org.elasticsearch.transport : [Mondo] bound_address {local[1]}, publish_address {local[1]}
2015-12-13 20:07:35.115 INFO 1 --- [ main] org.elasticsearch.discovery : [Mondo] elasticsearch/WwlYnRlVRsugFcnj37AL3g
2015-12-13 20:07:35.137 INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.service : [Mondo] new_master [Mondo][WwlYnRlVRsugFcnj37AL3g][raspberrypi][local[1]]{local=true}, reason: local-disco-initial_connect(master)
2015-12-13 20:07:35.351 INFO 1 --- [ main] org.elasticsearch.node : [Mondo] started
2015-12-13 20:07:36.743 INFO 1 --- [pdateTask][T#1]] org.elasticsearch.gateway : [Mondo] recovered [0] indices into cluster_state
2015-12-13 20:07:39.658 INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata : [Mondo] [device] creating index, cause [api], shards [1]/[0], mappings []
2015-12-13 20:07:42.427 INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata : [Mondo] [device] create_mapping [devicedescriptor]
2015-12-13 20:07:48.230 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d15089: startup date [Sun Dec 13 20:06:45 UTC 2015]; root of context hierarchy
2015-12-13 20:07:48.972 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.findByDevicId(java.lang.String)
2015-12-13 20:07:48.976 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[DELETE],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.devicemanagmeent.DeviceResource.deleteDeviceById(java.lang.String)
2015-12-13 20:07:48.978 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices],methods=[POST],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.createDevice(com.armzilla.ha.api.Device)
2015-12-13 20:07:48.980 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[PUT],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.updateDevice(java.lang.String,com.armzilla.ha.api.Device)
2015-12-13 20:07:48.982 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.util.List<com.armzilla.ha.dao.DeviceDescriptor>> com.armzilla.ha.devicemanagmeent.DeviceResource.findAllDevices()
2015-12-13 20:07:48.987 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/upnp/{deviceId}/setup.xml],methods=[GET],produces=[application/xml]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.upnp.UpnpSettingsResource.getUpnpConfiguration(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:48.996 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.String>> com.armzilla.ha.hue.HueMulator.getUpnpConfiguration(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:48.998 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights/{lightId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.api.hue.DeviceResponse> com.armzilla.ha.hue.HueMulator.getLigth(java.lang.String,java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.000 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights/{lightId}/state],methods=[PUT]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.hue.HueMulator.stateChange(java.lang.String,java.lang.String,javax.servlet.http.HttpServletRequest,java.lang.String)
2015-12-13 20:07:49.002 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/*],methods=[POST],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.hue.HueMulator.postAPI(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.004 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.api.hue.HueApiResponse> com.armzilla.ha.hue.HueMulator.getApi(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.017 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.020 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.451 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:49.452 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:50.016 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:54.574 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/env/{name:.*}],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2015-12-13 20:07:54.575 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/env],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.579 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/metrics/{name:.*}],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2015-12-13 20:07:54.580 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/metrics],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.583 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/dump],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.587 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/autoconfig],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.589 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/beans],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.592 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/health]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2015-12-13 20:07:54.595 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/mappings],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.598 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/trace],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.600 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/configprops],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.603 INFO 1 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/info],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:55.070 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2015-12-13 20:07:55.131 INFO 1 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0
2015-12-13 20:07:55.862 INFO 1 --- [pool-1-thread-1] com.armzilla.ha.upnp.UpnpListener : Starting UPNP Discovery Listener
2015-12-13 20:07:56.449 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-12-13 20:07:56.459 INFO 1 --- [ main] com.armzilla.ha.SpringbootEntry : Started SpringbootEntry in 73.894 seconds (JVM running for 77.281)
2015-12-13 20:08:00.959 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2015-12-13 20:08:00.960 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2015-12-13 20:08:01.267 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 305 ms
2015-12-13 20:14:26.643 INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata : [Mondo] [device] update_mapping [devicedescriptor] (dynamic)
Und wie erwartet bekomme ich eine UI wenn ich folgende URL von außen aufrufe:
http://raspberrypi:8080/configurator.html
Dieser Dienst soll übrigens eine Phillips Hue Bridge simulieren können, was für mein Mi-Light Projekt (vgl. anderen Artikel) interessant werden könnte, da ich diese auch gerne über die Hue-Apps steuern möchte.
Aber dies ist eine andere Geschichte... 😉

