How to detect if docker container crashed - java

I am running a process every 5 minutes and checking to make sure every container. If the container does not respond I can flag it as down. I have the containers IP address and I loop through each ip and check if it responds to a ping. If not I flag it as down. Is there a better way to do this? My code:
#Transactional
#Scheduled(fixedRate = 1000 * 60) //5 min
public void monitorHosts(){
Iterable<Ncl> ncls = nclRepository.findAll();
for(Ncl ncl: ncls){
for(String host: ncl.getHosts()){
Boolean isHostAlive = isHostAlive(host);
if(!isHostAlive){
Ncl nclWorking = nclRepository.findOne(ncl.getUuid());
if(nclWorking != null){
Set<String> hosts = nclWorking.getHosts().stream().filter(x -> x.equals(host)).collect(Collectors.toSet());
nclWorking.getHosts().clear();
nclWorking = nclRepository.save(nclWorking);
nclWorking.setHosts(hosts);
nclRepository.save(nclWorking);
}
}
}
}
}
private Boolean isHostAlive(String host){
try{
InetAddress address = InetAddress.getByName(host);
boolean reachable = address.isReachable(10000);
return reachable;
} catch (Exception e){
e.printStackTrace();
return false;
}
}

It mostly depends on what you need to do with the information about your containers.
There is a number of monitoring solutions available, which can monitor your containers and notify some one if there are some troubles.
If you have to use this info in some application, then you can use some solutions like Consul.io and let them check your services statuses, not containers (in most cases man aware of the service availability in the container, not container itself). Or you can use docker-api for Java, because ICMP-protocol is not always a good solution, especially in distributed networks.

I would use docker events
extract from the doc
https://docs.docker.com/engine/reference/commandline/events/#examples
use something like
docker events --filter 'event=stop'

This is more obtrusive but is a nice one:
Using HEALTHCHECK that docker provides
You can customize it for each application. Something like:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
Then you can check the health status as this:
docker inspect --format='{{json .State.Health}}' <container_id>
Or you can use docker ps and see the STATUS column:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
225426fc7c93 ubuntu "tail -f /dev/null" 5 seconds ago Up 4 seconds (healthy)
Or you can query the docker API:
curl --unix-socket /var/run/docker.sock http:/v1.24/containers/json
PS: You can set the healthcheck at docker run time (without modifying the Dockerfile). Docs

Related

Test containers: ignore parent `EXPOSE` instruction from Dockerfile

I'm trying to run Couchbase v.5.1.1 docker container for test purposes via Test container with fixed exposed ports, like:
trait CouchbaseTestEnvironment extends ForAllTestContainer {
this: Suite =>
def couchbaseContainer: FixedHostPortGenericContainer = {
val consumer = new Slf4jLogConsumer(LoggerFactory.getLogger(getClass))
/*
* Couchbase should know which ports are exposed for client, because this is how it exposes services.
* E.g. client ask only for on port - say 8091. And query service port is 8093. So client, won't ask for every port,
* instead CB will tell client on which port query service exposed, that's why CB should be aware about port mapping.
* That's why we need to give CB port mappings
*
* See for more details:
* https://stackoverflow.com/questions/59277436/couchbase-in-docker-for-integration-tests-make-the-ports-8092-8093-8094-and-8
*/
def randomPort: Int = {
val (from, to) = (32768, 35000) //linux private port range
from + Random.nextInt(to - from)
}
val random8091Port = randomPort
val random8092Port = randomPort
val random8093Port = randomPort
val random8094Port = randomPort
val random11210Port = randomPort
val container = FixedHostPortGenericContainer(
imageName = "couchbase:community-5.0.1",
exposedHostPort = random8091Port,
exposedContainerPort = random8091Port,
env = Map(
"COUCHBASE_RANDOM_PORT_8091" -> random8091Port.toString,
"COUCHBASE_RANDOM_PORT_8092" -> random8092Port.toString,
"COUCHBASE_RANDOM_PORT_8093" -> random8093Port.toString,
"COUCHBASE_RANDOM_PORT_8094" -> random8094Port.toString,
"COUCHBASE_RANDOM_PORT_11210" -> random11210Port.toString
)
)
container.container.withFixedExposedPort(random8092Port, random8092Port)
container.container.withFixedExposedPort(random8093Port, random8093Port)
container.container.withFixedExposedPort(random8094Port, random8094Port)
container.container.withFixedExposedPort(random11210Port, random11210Port)
container.container.withLogConsumer(consumer)
container
}
}
So as you can see 5 FIXED ports should be exposed.
But, when I'm running tests I actually can see, that instead other ports exposed with random ports:
docker ps
f4fc1ce06544 couchbase:community-5.0.1 "/entrypoint.sh /opt…" 59 seconds ago Up 1 second 0.0.0.0:55264->8091/tcp, 0.0.0.0:55263->8092/tcp, 0.0.0.0:55262->8093/tcp, 0.0.0.0:55261->8094/tcp, 0.0.0.0:55260->11207/tcp, 0.0.0.0:55259->11210/tcp, 0.0.0.0:55258->11211/tcp, 0.0.0.0:55257->18091/tcp, 0.0.0.0:55256->18092/tcp, 0.0.0.0:55255->18093/tcp, 0.0.0.0:55254->18094/tcp unruffled_mendel
03b491ac2ea8 testcontainersofficial/ryuk:0.3.0
So as you can see another ports was exposed, and mapped to random ports instead fixed.
As far as I understand, test containers, ignores ports I gave, and instead exposes ports from Couchbase Dockerfile: https://github.com/couchbase/docker/blob/master/community/couchbase-server/5.1.1/Dockerfile#L74
EXPOSE 8091 8092 8093 8094 8095 8096 11207 11210 11211 18091 18092 18093 18094 18095 18096
Can I somehow force Test containers to ignore EXPOSE instruction?
Partially helped question: Couchbase in docker for integration tests: Make the ports 8092, 8093, 8094 and 8095 configurable to be able to use docker’s random ports
Can I somehow force Test containers to ignore EXPOSE instruction?
I don't know if there is a simple configuration option for this, but a workaround solution I found is to use an advanced feature of the docker-java create container command customization. I'm providing an example in Java, translate it to Scala yourself, please. Apply it as the last command before returning a container object from your function:
container.withCreateContainerCmdModifier(
cmd -> cmd.getHostConfig().withPublishAllPorts(false)
);
The main point here is the usage of .withPublishAllPorts(false). From my understanding, this is the same as --publish-all (or -P) arguments of the docker run command. Testcontainers library sets this value to true by default. This modification overrides it to false.
With this configuration no ports are published at all for your example, not the 5 fixed as expected:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2ee4fb91b97c couchbase:community-5.0.1 "/entrypoint.sh couc…" 33 seconds ago Up 32 seconds 8091-8094/tcp, 11207/tcp, 11210-11211/tcp, 18091-18094/tcp trusting_keldysh
This is because in the answer you provided, the author created a special custom docker image of couchbase, which "understands" environment variables like COUCHBASE_RANDOM_PORT_8091. Your code uses the standard couchbase image couchbase:community-5.0.1, which basically just ignores these environment variables. So in order to run counchbase on not standard internal ports, you need to build a custom image with the "magic" configure-node.sh script, which tunes couchbase config using values provided in environment variables.
I hope it helps anyhow :)

How to get the automatically defined port for a Spark Java Application?

In the API documentation for Java Spark (not Apache spark), you can specify a port of 0 to have it automatically select a port. Great!
However, I cannot figure out how to get that port after the server is started. I can see it in the logs:
15:41:12.459 [Thread-2] INFO spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:63134
But I need to be able to get to it programmatically, so that my integration tests are able to run reliably every time.
So how do I get that port?
I could find no way to get this information in the API, and so I filed an issue on their github.
I was able to get at it via an ugly pile of reflection:
/**
* Meant to be called from a different thread, once the spark app is running
* This is probably only going to be used during the integration testing process, not ever in prod!
*
* #return the port it's running on
*/
public static int awaitRunningPort() throws Exception {
awaitInitialization();
//I have to get the port via reflection, which is fugly, but the API doesn't exist :(
//Since we'll only use this in testing, it's not going to kill us
Object instance = getInstance();
Class theClass = instance.getClass();
Field serverField = theClass.getDeclaredField("server");
serverField.setAccessible(true);
Object oneLevelDeepServer = serverField.get(instance);
Class jettyServerClass = oneLevelDeepServer.getClass();
Field jettyServerField = jettyServerClass.getDeclaredField("server");
jettyServerField.setAccessible(true);
//Have to pull in the jetty server stuff to do this mess
Server jettyServer = (Server)jettyServerField.get(oneLevelDeepServer);
int acquiredPort = ((ServerConnector)jettyServer.getConnectors()[0]).getLocalPort();
log.debug("Acquired port: {}", acquiredPort);
return acquiredPort;
}
This works well for me in our integration tests, but I'm not using https, and it does reach about two levels deep into the API via reflection grabbing protected fields. I could not find any other way to do it. Would be quite happy to be proven wrong.
This will work on Spark 2.6.0:
public static int start (String keystoreFile, String keystorePw)
{
secure(keystoreFile, keystorePw, null, null);
port(0);
staticFiles.location("/public");
get(Path.CLOCK, ClockController.time);
get(Path.CALENDAR, CalendarController.date);
// This is the important line. It must be *after* creating the routes and *before* the call to port()
awaitInitialization();
return port();
}
Without the call to awaitInitialization() port() would return 0.

How to programmatically connect internet via datacard with AT commands?

I have a datacard ZTE MF190. I want to use AT commands to register in 2G or 3G and access internet via datacard. Found this article about how to make data call:
AT+cgatt=1
AT+CGDCONT=1,”IP”,”epc.tmobile.com” //I used my operator PDP context
AT+CGACT=1,1
But ping from OS terminal shows 100% package loss.
I've tried on Ubuntu 14 and Windows 7.
How can I connect internet with AT commands using datacard on Ubuntu?
UPDATE
I gave bounty to #tripleee's answer because it's more full than first one and answered all my questions. But I'm not satisfied with answers, so I'll answer my own question in a week.
In my answer I'll show how to handle this process with Java. So, please do not move this question to other Stack Exchange websites.
Creating a connection between the card and your provider is not sufficient. You need some mechanism for creating a network interface out of this connection, and set up your network stack to route packets over this interface.
Traditionally, the pppd daemon has been a popular choice for this task. You would create a "chat script" with the commands for establishing a data call (these days, pppd might come packaged with a suitable canned script) and the daemon would handle the entire process of placing the call, authenticating, setting up a network interface over the circuit, and configuring the system to route packets over it, as well as configuring DNS etc to use it for resolver queries, etc.
I tried to sniff USB port but on this case dashboard can not connect because of busy port
It is certainly possible. See this question
Found this article about how to make data call
What that article is about is how to set up the call, not how to make it.
After you made correct setup, connect to internet with this command:
ATD*99***1#
UPDATE1: After a bit of research I believe that article was written only to promote their software and has no practical use. In reality dialing is made with pppd or wvdial
UPDATE2: We discussed ways to solve the problem in a chat room (in Russian). It turned out cnetworkmanager will be the way to go
As far as I know wvdial uses ppp daemon to connect to the internet using modem. wvdial is preinstalled on desktop version of Ubuntu.
wvdial uses a config file located /etc/wvdial.conf. Let's edit this file. Type in your terminal
sudo nano /etc/wvdial.conf
and you will see something like this
[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2
Stupid Mode = yes
ISDN = 0
Modem Type = Analog Modem
New PPPD = yes
Phone = *99#
Modem = /dev/ttyUSB2
Username = ''
Password = ''
Baud = 9600
Dial Timeout = 30
Dial Attempts = 3
Explanation of all keys you can find in wvdial.conf(5) - Linux man page. If you need to change your provider dial number, username, password or any other information about connection and device you can change file content and save it.
There are 3 serial ports for ZTE MF190. Normally it's ttyUSB0, ttyUSB1 and ttyUSB2. And in my case ttyUSB2 is for internet connection. It would not work on other ports. So you need to find the right serial port for your modem.
There is an automatic configurator which edits wvdial.conf file, sets serial port baud rate etc. Since it is not always configure correctly I would not recommend to use it:
sudo wvdialconf /etc/wvdial.conf
It would be better if you configure wvdial manually.
Now, when your device connected and wvdial configured to work with device, you can execute this line from terminal:
wvdial
You will see a lot of lines. But if you see those lines - you have succeeded.
local IP address XX.XX.XX.XX
remote IP address XX.XX.XX.XX
primary DNS address XX.XX.XX.XX
secondary DNS address XX.XX.XX.XX
Now, how we can use it in programming? I'll provide some code to work with it on Java. You can use this code to dial.
public int dialer() {
// status for debug. If status == 4 then you connected successfully
int status;
// create process of wvdial
ProcessBuilder builder = new ProcessBuilder("wvdial");
try {
// start wvdial
final Process process = builder.start();
// wvdial listener thread
final Thread ioThread = new Thread() {
#Override
public void run() {
try {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
// wvdial output line
String line;
while ((line = reader.readLine()) != null) {
// if "local IP address" line detected set status 1
if (line.contains("local IP address")) {
status = 1;
}
if (line.contains("remote IP address")) {
status = 2;
}
if (line.contains("primary DNS address")) {
status = 3;
}
if (line.contains("secondary DNS address")) {
status = 4;
}
}
reader.close();
} catch (final Exception e) {
}
}
};
// start listener
ioThread.start();
// wait 6 secs and return status. Some kind of timeout
Thread.sleep(6000);
} catch (Exception e) {
}
return status;
}
And here is a disconnector method. All you need is to kill wvdial process and thread will be destroyed:
public boolean disconnect() {
ProcessBuilder builder = new ProcessBuilder("pkill", "wvdial");
try {
builder.start();
return true;
} catch (IOException e) {
return false;
}
}

check accessibility of all nodes in a network in java

I want to check all of nodes accessibility in a network using java. I have read This and This Questions and I have write my method by helping these questions, I have at least two tested Ips in my network which are accessible, one of them is 192.168.1.1 and another is 192.168.1.102 you can see test of 192.168.1.1 in the below picture:
Odk but when I run my code my app says that 192.168.1.1 is not reachable. here is the image, I have pointed at it with a red arrow:
ok , and here is my java code:
private void checkNetworkAccessibility(){
int timeout = 1000;
String subnet = firstSubnet.getText() + "." + secondSubnet.getText() + "." + thirdSubnet.getText() + ".";
DefaultTableModel model = (DefaultTableModel)networkTable.getModel();
for(int i=1;i<=254;i++){
try {
if(InetAddress.getByName(subnet+i).isReachable(timeout)){
model.addRow(new Object[]{subnet + i, subnet + i, "بله", "بله"});
}
else{
model.addRow(new Object[]{subnet + i, "نامشخص", "خیر", "بله"});
}
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
what is wrong with my code?
The problem is that .isReachable() is not reliable. Its implementation is totally OS dependent!
Let's take Linux as an example; this method uses the echo TCP service (port 7). Do you actually know of a server which has even that running today? I don't.
It cannot use ping; look at this:
$ ls -l $(which ping)
-rwsr-xr-x 1 root root 44168 May 7 2014 /bin/ping
Yes, that's right; the sticky bit. The reason for this is that the ping command sends an ICMP echo request packet along the wire, and this requires that you be able to access raw sockets.
Which you can't do unless you have the necessary privileges, and which you can't do in Java! Except if you use native libraries. And you run with the necessary privileges. Which more often than not means you need to be admin. Which you don't want.
And of course, a sysadmin may even decide to block incoming echo request packets on a host, so even ping is not reliable...

How to connect to custom network when building image/container in Docker-Java API

I am using the Docker-Java API found here https://github.com/docker-java/docker-java.
I have a dockerfile that I have to build 3 containers for. I am trying to automate the process of building 3 images and the commands to go with them when running the shell inside. The 3 containers must be in the same local network in order for them to communicate. I am able to do this manually just fine...
So first, using the docker-java API, I am building a custom network using the following function:
private void createNetwork() {
CreateNetworkResponse networkResponse = dockerClient.createNetworkCmd()
.withName("ETH")
.withDriver("bridge")
.withAttachable(true)
.exec();
System.out.printf("Network %s created...\n", networkResponse.getId());
}
This works great, and if I run docker network ls, I can see the ETH network listed.
The next step is building the image. I am running the following function:
public String buildImage(String tag) {
String imageID = dockerClient.buildImageCmd()
.withDockerfile(new File("/Dockerfile"))
.withPull(true)
.withNoCache(false)
.withTags(new HashSet<>(Collections.singletonList(tag)))
.withNetworkMode("ETH")
.exec(new BuildImageResultCallback())
.awaitImageId();
System.out.println("Built image: " + imageID);
return imageID;
}
So the image builds fine and I can see the image when I run the docker images command in terminal. I do expect that the image to be connected to the ETH network, but I do not see that.
I thought that maybe I have to connect to the network when creating the container instead then, so I pass the same commands I would if I were to manually do this when building the container through the following function:
private String createContainer(String name, String imageID, int port) {
CreateContainerResponse container = dockerClient
.createContainerCmd(name)
.withImage(imageID)
.withCmd("docker", "run", "--rm", "-i", "-p", port + ":" + port, "--net=ETH", name)
.withExposedPorts(new ExposedPort(port))
.exec();
dockerClient.startContainerCmd(container.getId()).exec();
return container.getId();
}
Unfortunately, when passing in the arguments like this, the built container does not show up in the ETH network when running the command docker network inspect ETH.
I'm not sure what I am doing wrong. If I build the image using the API, and then run the following command manually, docker run --rm -it -p 8545:8545 --net=ETH miner_one everything works fine. Any help would be greatly appreciated. Thank you!
The docker-java client supports a subset of the Docker Remote API. To connect to a network when you create the container set the NetworkMode field
(see HostConfig -> NetworkMode in Container Create section)
Network mode to use for this container. Supported standard values are:
bridge, host, none, and container:. Any other value is taken
as a custom network's name to which this container should connect to.
Therefore, in order for the container to connect to the custom network set the value of the network mode to ETH.
In Java, for older versions of the Docker-Java client, use the withNetworkMode() method:
CreateContainerResponse container = dockerClient
.createContainerCmd(name)
.withImage(imageID)
.withNetworkMode("ETH")
...
In the latest version, the methods in CreateContainerCmd used to set the fields in HostConfig are deprecated. Use withHostConfig() instead:
CreateContainerResponse container = dockerClient.createContainerCmd(name)
.withImage(imageID)
.withHostConfig(HostConfig.newHostConfig().withNetworkMode("ETH"))
...
Here is a basic example:
List<Network> networks = dockerClient.listNetworksCmd().withNameFilter("ETH").exec();
if (networks.isEmpty()) {
CreateNetworkResponse networkResponse = dockerClient
.createNetworkCmd()
.withName("ETH")
.withAttachable(true)
.withDriver("bridge").exec();
System.out.printf("Network %s created...\n", networkResponse.getId());
}
CreateContainerResponse container = dockerClient
.createContainerCmd("ubuntu")
.withName("my-ubuntu")
.withCmd("sleep", "10")
.withHostConfig(HostConfig
.newHostConfig()
.withNetworkMode("ETH")
.withAutoRemove(true))
.exec();
String containerId = container.getId();
dockerClient.startContainerCmd(containerId).exec();
Network ethNetwork = dockerClient.inspectNetworkCmd()
.withNetworkId("ETH")
.exec();
Set<String> containerIds = ethNetwork.getContainers().keySet();
if(containerIds.contains(containerId)) {
System.out.printf("Container with id:%s is connected to network %s%n", containerId, ethNetwork.getName());
}
It creates a network named ETH and a container my-ubuntu from an ubuntu image. The container is connected to the ETH network.
Hope this helps.

Categories

Resources